TFS Work Item Hierarchy

Team Foundation Server doesn't natively support work item hierarchy, we all know that.  So the question is, how can you create a reasonable workaround to get a work item hierarchy? 

Of course, all of these techniques require custom reporting, either some fancy SQL Report MDX code, or your own reporting app.  And there isn't easy editing built into Team Explorer or Excel, but it is relatively easy to add the infrastructure to the work item types, then write your own little app to display the hierarchy.

Here are some ideas...

Note: This is not a "best practice" or "supported scenario", it is a workaround.  The next version of VSTS may very well support this scenario.  Until then...

Create Specific Work Item Types

This is what we use internally for our "Feature Directory" TFS Project.  We have completely different work item types (WITs) that implicitly derive a hierarchy by linking the different WITs together.  We have a "Value Proposition", an "Experience", and a "Feature" WIT.  The hierarchy goes Value Proposition are at the top, the next level are Experiences, then Features are at the bottom.

Add a "Parent ID" Field to the Work Item

This requires a small edit to your WITs.  You can simply add a "Parent ID" field to your WIT.  Then when you create a new work item, just put the ID of it's parent in the "Parent ID" field.  This is the common self-referring one-to-many relational database trick.

Link Work Items Together

This doesn't require any changes to your WITs.  If you have a single "Root" work item, then you can link the 2nd tier of work items to that one, the 3rd tier to the 2nd tier, etc.  It may look like a flat list of links in the work item, but it makes a nice hierarchy when you print it out.  Here is the code to do just this, but with a list of root nodes.

Sample Code: ListLinkedWorkItems.zip

// Note: Look for output in the Output window

using System;

using System.Collections.Generic;

using Microsoft.TeamFoundation.Client;

using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace ListLinkedWorkItems

{

   class Program

   {

      static void Main()

      {

         // Opperational parameters

         string server = "https://somevstsserver01:8080";

         string RootList = "158627,158637,158638,158642,158645";

        

         // Create a list of IDs out of the string

      List<int> root = new List<int>();

         foreach (string id in RootList.Split(',')) root.Add(int.Parse(id));

         // Obtain a reference to the server factory

         TeamFoundationServer tfs =

            TeamFoundationServerFactory.GetServer(server);

         // Obtain the work item store

         WorkItemStore store =

            tfs.GetService(typeof(WorkItemStore)) as WorkItemStore;

         // List to maintain which work item IDs have been visited

         List<int> chain = new List<int>(root);

         // Iterate through a list of work item IDs

         foreach (int id in root)

            ShowFullHierarchy(store, id, chain, 0);

      }

      private static void ShowFullHierarchy(

         WorkItemStore store, int parent, List<int> chain, int level)

      {

         // List of the current work item's linked work items

         List<int> subitems = new List<int>();

         // Placeholder for a link that is a related work item

         RelatedLink link;

         // Obtain the current work item from its ID

         WorkItem root = store.GetWorkItem(parent);

         // Show the user the current work item

         WriteWorkItem(root, level);

         // Obtain all linked WIs that haven't been visited yet

         foreach (Link wiLink in root.Links)

            if ((link = wiLink as RelatedLink) != null)

               if (!chain.Contains(link.RelatedWorkItemId))

                  subitems.Add(link.RelatedWorkItemId);

         // Add the newly linked items to the list that's been touched

         chain.AddRange(subitems);

         // Show all newly linked items

         foreach (int id in subitems)

            ShowFullHierarchy(store, id, chain, level + 1);

      }

      private static void WriteWorkItem(WorkItem wi, int indent)

      {

         // Display a work item in a formatted way

         System.Diagnostics.Trace.WriteLine(

            String.Format("{0}{1,7} {2}",

            "".PadLeft(indent * 2), wi.Id, wi.Title));

      }

   }

}

This produces a list that looks something like this.  Note that this list has been modified from it's original state and is now just a sample report.

 158627 Figure out a way to make Debugger messaged more visible to the user

   161457 Suggestion: Remote Debugging Setup Wizard

   113967 Make the options on the 'exception thrown' dialog more descriptive

 158637 Debugger Visualizers

   114004 Additional hex/text visualizer would be useful

 158638 Remove Customer Dissatisfiers

   171879 Modify managed symbol provider to locate static members for interop debugging.

     112589 DTS: SRX060227603615 - Error: symbol 'i' not found' for a local static

     171458 DTS:SRX061115601437 VS 2005 C++ app debug watch wont show class static

   113974 Debugger does not load symbols for all appdomains when one module is used

   114015 Attach to Process dialog should automatically refresh process list.

      64926 Attach to Process dialog should automatically refresh process list.

   148715 Allow "module loaded/unloaded" message in debugger to be disabled

   125068 Just-in-time debugger after uninstallation

 158642 Debugger Help, Support, Troubleshooting

 158645 Concord

   113973 cpde needs to drain debug events from all threads when it receives an event

      74929 User should see prompt when user-unhandled exception kills stepper

        71708 DTS SRZ060223001628 - Debugger doesn't step through if runing a VB client

      75317 cpde needs to drain debug events from all threads when it receives an event

   114052 "Changes are not allowed when debugger has been attached" dialog disrupts flow

      69261 DTS SRX060215601001 Debugger: Unable to edit class library in VB with ASP.net

   127479 Breakpoints don't work in mixed debugger when inside InternalCall methods

Taking it a Step Further

Some additional ideas to take this to the next level:

  • Put the display into a web page (or SharePoint part)
  • Save the results as an XML file that can be viewed & grouped in Excel, or displayed with a nice XSL->HTML 
  • Make the app a command line app (or better yet, a PowerShell commandlet)
  • Use Crystal Reports to code a custom report 

Resources