TFS11 API: Customizing the Process Configuration

Customizing the Process Configuration

Most key aspects of your team project are defined by the process template you are using. The process template itself is a collection of files and metadata which descibe how individual components of your team project work. Some of these components include the available work item types and their state transitions, default work item queries, reports, security groups, version control settings, build definitions etc. A special part of the of the team project setup is the process configuration, which you can customize to meet your ALM needs and requirements. Working with this subset of features is exactly what this blog post is going to cover. For more information on customizing the process configuration, check out the MSDN documentation. If you are interested, the full source code for the entire blog post can be found here.

Overview

Since we are going to cover a lot of information today, I’ll start by giving you a short overview of things to come. First, we’ll discover how to read the name of the process template our team project is based on. Then, we’ll see how to configure the working days for the team project, since this can be important to know for capacity planning.

Next, we introduce the CommonProjectConfiguration class, and explore the process configuration features it exposes: work item type categories and work item states and metastates. We’ll end this part of the post by exploring process configuration work item fields.

In the final part of the blog post, we’ll see what the AgileProjectConfiguration class has to offer. This will include retrieving the backlog columns and their widths, as well as fields that appear on the Quick-Add work item panel in the Product Backlog. Let’s get to it, shall we?

Reading the process template name

We can retrieve the process template name for a specified team project by using classes and services we’ve already introduced in earlier posts from the TFS11 API series. Specifically, we only need to utilize the common structure service. Here’s how:

string ReadProcessTemplateName(TfsTeamProjectCollection tfs, string projectName)
{
    var css = tfs.GetService<ICommonStructureService4>();
    string projectUri = css.GetProjectFromName(projectName).Uri;
    
    // Try to retrieve the process template property.
    var template = css.GetProjectProperty(projectUri, "Process Template");
    
    if (template != null && !string.IsNullOrEmpty(template.Value))
        return template.Value;
    
    return null;
}

As you can see, there’s really not much to it. We simply use the common structure service to query for a property with a specified name (“Process Template”) while providing the team project URI. If this property exists (and it should), we simply return it’s value.

Getting more info

If we want to work with more process configuration settings, we’ll need to retrieve an instance of the ProjectProcessConfigurationService class. We can then use this service for getting, validating and setting both the common and agile configuration objects. The following snippet demonstrates how to obtain an instance of the service, as well as get both the common and agile configuration objects:

var cfgService = tfs.GetService<ProjectProcessConfigurationService>();

CommonProjectConfiguration commonCfg = cfgService.GetCommonConfiguration(projectUri);
AgileProjectConfiguration agileCfg = cfgService.GetAgileConfiguration(projectUri);

Not much to look at here. We’ll be working with the CommonProjectConfiguration object and the AgileProjectConfiguration object a bit later.

Reading the working days

Lets now have a look at how to know which days of the week our teams are actually working. You might be interested in this data if you are drawing a burndown chart, where you would most probably like to exclude the days your team members are not working at all. This would eliminate the flat lines that would occur on non-working days and it might also help improve performance, since you shouldn’t expect any changes to happen those days. Additionally, this data might prove useful if you are building a project management tool or capacity planning tool yourself, such as WebAccess or TeamCompanion. Anyways, for whatever reason you might care, here’s the code:

void ReadWorkingDays(CommonProjectConfiguration commonCfg)
{
    IEnumerable<DayOfWeek> weekends = commonCfg.Weekends;

    // Unwnid the enum into a sequence of possible values.
    var allValues = Enum.GetValues(typeof(DayOfWeek)).Cast<int>();
	var allDays = allValues.Select(d => (DayOfWeek)d);

    // Exclude the non-working days and construct a string for display.
    var workingDays = allDays.Except(weekends);
    string str = workingDays.Aggregate("", (a, d) => a = string.Concat(a, " ", d));

    Console.WriteLine("Working days: {0}", str);
}

For example, if the Weekends property contained Saturday and Sunday (as it does by default), the output string would contain all the days of the week from Monday through Friday.

Work item type categories

For a detailed explanation of what exactly work item type categories are, I would strongly recommend reading the following MSDN article. While the article describes what categories are and how they should be used, the examples for working with the settings are using XML configuration files. We must see how these map to the client API:

void ReadWorkItemTypes(TfsTeamProjectCollection tfs)
{
    var cfgService = tfs.GetService<ProjectProcessConfigurationService>();
    var commonCfg = cfgService.GetCommonConfiguration(_projectUri);

    // Retrieve all the available non-empty WI categories.
    IEnumerable<WorkItemCategory> categories = new[]
    {
        commonCfg.BugWorkItems, commonCfg.FeedbackRequestWorkItems,
        commonCfg.FeedbackResponseWorkItems, commonCfg.FeedbackWorkItems,
        commonCfg.RequirementWorkItems, commonCfg.TaskWorkItems
    }.Where(c => c != null);

    // Retrieve the project object, because it holds the actual
    // work item types per category. The common configuration object
    // only holds a reference to the category object.
    WorkItemStore workItemStore = new WorkItemStore(tfs);
    Project project = workItemStore.Projects[_projectName];

    foreach (WorkItemCategory cat in categories)
    {
        // Find a matching category object in the 
        // project's categories collection.
        var pc = project.Categories
            .FirstOrDefault(c => c.ReferenceName.Equals(cat.CategoryName));
        
        if (pc != null)
        {
            // Output the display and reference names of the category.
            Console.WriteLine("{0} ({1}):", pc.Name, pc.ReferenceName);
            
            foreach (WorkItemType wiType in pc.WorkItemTypes)
            {
                // One of the work item types must be default.
                bool isDefault = (wiType == pc.DefaultWorkItemType);
                
                // Output the WI type name and whether it's the deafult.
                Console.WriteLine("  {0}{1}", wiType.Name, 
                    isDefault ? " (default)" : string.Empty);
            }
        }
    }
}

The code from the snippet simply iterates over all the available work item type categories and outputs the work item types from each category. Additionally, it denotes which work item type is the default one in the category. If you try running this snippet in your own environment, you’ll probably notice that most work item categories contain only a single work item type. One exception to this might be the Requirements category if you’re using a Scrum template, where you’d have a Product Backlog Item type and Bug work item type as child types. Furthermore, should you have more then one work item in a category, one of them has to be the default one.

To better explain where the default work item types come into play, take a look at this screenshot taken from Team WebAccess, which displays the team home page:

The team homepage

This is a nice example of how the work item categories are used throughout TFS client applications. As you can see, the default work item types from the Requirement (red), Task (green) and Bug (blue) categories are displayed on the toolbar. Additionally, the Requirement and Task categories determine the work items that will be displayed inside the backlogs and the task board. More precisely, only the work item types from the Requirements category will show up in the Product Backlog. In the Iteration (Sprint) Backlog, you would see work items from both the Requirements and the Tasks categories. Lastly, you would see only work items from the Tasks category while viewing work items on the Task Board.

I would just like to point a few things out (although just repeating some of the ones outlined in the MSDN documentation) when assigning work item types to categories:

  • You must assign at least one work item type to the Requirements category and one to the Task category.
  • The same work item type cannot be assigned to both the Requirements and the Tasks categories.
  • If you include more than one work item type in a category, be sure to specify the default one.
  • For each work item type you assign to a category, you must define a valid metastate for each of the possible work item workflow states (coming right up).

Alright, now let’s see what this workflow state and metastate stuff is all about.

Work item states and metastates

Once again, I’d encourage you to read through the MSDN documentation section regarding the workflow states and metastates. It’s really nice to see some well written TFS documentation, especially since the product is still in beta!

To summarize things a bit: a workflow state is any state that the work item can transition into. The available work item states depend on the work item type and you can freely define your own. Their values are Active, Resolved, Closed, Completed, To Do, or New, just to name a few. These states provide no useful information to your TFS server, where they are just stored as simple strings.

The metastate does, however, provide information to TFS and this information is used to determine whether a work item should or should not be display in the backlog or the task board. There are only four available metastates: Proposed, InProgress, Complete and Resolved. The first three metastates can be mapped to any worfklow state of any work item type in any category. The fourth metastate (Resolved) can only be mapped to workflow states of work items types in the Bug category.

Let’s see a code snippet that demonstrates how to retrieve the metastates, and then we’ll examine how TFS (WebAccess) uses that information:

void ReadWorkItemStates(IEnumerable<WorkItemCategory> categories)
{
    foreach (WorkItemCategory cat in categories)
    {
        Console.WriteLine("{0}: {1} states(s)", 
            cat.CategoryName, cat.States.Length);
        
        foreach (State state in cat.States)
            Console.WriteLine("  {0} ({1})", state.Value, state.Type);

        Console.WriteLine();
    }
}

Before going into any more detail, let’s see what the output is for the Requirements and Tasks categories:

Microsoft.RequirementCategory: 4 states(s)
  New (Proposed)
  Approved (Proposed)
  Committed (InProgress)
  Done (Complete)

Microsoft.TaskCategory: 3 states(s)
  To Do (Proposed)
  In Progress (InProgress)
  Done (Complete)

We’ve said before that only work item types from the Requirements category show up in the Product Backlog. Additionally, the work item’s state must map to the Proposed metastate in order for it to be visible in the Product Backlog. As soon as the work item state changes so that the corresponding state is no longer Proposed, it will be removed from the backlog. Having said this, the Proposed metastate indicates work items that are new, not yet committed, or not yet being worked on.

Similarly, work items that belong to either the Requirements or the Tasks category, and whose metastate is InProgress, will appear in the Iteration Backlog. So, this metastate indicates work items that have been committed or are actively being worked on.

And finally, the Complete metastate, which indicates work items that have been implemented. Items in this metastate are included in calculating the team’s velocity.

As a final note on work item metastates, keep in mind that if a work item workflow state is not mapped to a metastate, work items in that state will never show up in Team WebAccess.

Work item fields

There’s just one more piece of information that the CommonProjectConfiguration class exposes, and that’s the work item fields.

void ReadWorkItemFields(CommonProjectConfiguration commonCfg)
{
    foreach (TypeField typeField in commonCfg.TypeFields)
    {
        Console.WriteLine("Type  : {0}", typeField.Type);
        Console.WriteLine("Name  : {0}", typeField.Name);
        Console.WriteLine("Format: {0}", typeField.Format);
        Console.WriteLine("Values: {0}", typeField.TypeFieldValues.Length);
    
        foreach (TypeFieldValue fieldValue in typeField.TypeFieldValues)
            Console.WriteLine("  {0} ({1})", fieldValue.Value, fieldValue.Type);

        Console.WriteLine();
    }
}

That snippet wasn’t much to look at. All it does is iterate through the items in the TypeFields property and display some values to the console. Here’s an example of what the snippet output on my machine:

Type: Activity
Name: Microsoft.VSTS.Common.Activity
Format: '{0}'
Fields: 0

Type: Order
Name: Microsoft.VSTS.Common.BacklogPriority
Format: '{0}'
Fields: 0

Type: Effort
Name: Microsoft.VSTS.Scheduling.Effort
Format: '{0}'
Fields: 0

Name: Microsoft.VSTS.Feedback.ApplicationType
Type: ApplicationType
Format: '{0}'
Fields: 3
  Client application (ClientApp)
  Remote machine (RemoteMachine)
  Web application (WebApp)

Type: RemainingWork
Name: Microsoft.VSTS.Scheduling.RemainingWork
Format: '{0} h'
Fields: 0

Type: Team
Name: System.AreaPath
Format: '{0}'
Fields: 0

Alright, here’s a short rundown of what each of the field types represent. The Activity field indicates which work item field represents an activity or discipline (that is, development, testing, documentation etc.) Additionally, it is this field that the activity values are filled on the team member capacity pane:

Team member capacity and activity

The Order type field denotes the work item field that is used when sorting (ranking) requirement work items in the product backlog, while the Effort type field indicates which work item field keeps track of a requirements value (story points). The RemainingWork type field should be pretty self-explanatory: it denotes the work item field used to keep track of work that’s left on task work items. The last type field that I know what is’s for is the Team type field, which you can use to specify a work item field to use for determining which team a work item belongs to. By default, this is the System.AreaPath field.

The other available type fields (Requestor, ApplicationType, FeedbackNotes, ApplicationStartInformation, ApplicationLaunchInstructions) are used for request and reponse work items, as well as test cases (apparently). I haven’t had a chance to examine what these actually do yet, so I’m not going to go into any details with them. In some future blog post, perhaps?

Getting the backlog columns

Let’s immediately start off with some code. The snippet defines a method, to which we pass an instance of the process configuration service class, and which outputs the visible columns for the product and iteration backlogs:

void ReadBacklogColumns(ProjectProcessConfigurationService cfgSvc)
{
    var agileCfg = cfgService.GetAgileConfiguration(projectUri);
    ProductBacklogConfiguration backlogCfg = agileCfg.ProductBacklog;
    IterationBacklogConfiguration iterationCfg = agileCfg.IterationBacklog;

    var data = new[]
    {
        new { Name = "Product Backlog", Columns = backlogCfg.Columns },
        new { Name = "Iteration Backlog", Columns = iterationCfg.Columns },
    };

    foreach (var dataItem in data)
    {
        Console.WriteLine("{0} columns:\n", dataItem.Name);
        
        foreach (Column col in dataItem.Columns)
            Console.WriteLine("  {0} ({1}px)", col.FieldName, col.ColumnWidth);

        Console.WriteLine();
    }
}

I’ve run this code snippet against a team project based on the Microsoft Visual Studio Scrum 2.0 template and here’s what the output was:

Product Backlog columns:

  System.Title (400px)
  System.State (100px)
  Microsoft.VSTS.Scheduling.Effort (50px)
  System.IterationPath (200px)

Iteration Backlog columns:

  Microsoft.VSTS.Scheduling.Effort (50px)
  System.Title (400px)
  System.State (100px)
  System.AssignedTo (100px)
  Microsoft.VSTS.Scheduling.RemainingWork (50px)

Great! Now, let’s compare the output with the actual layout of the backlogs. First, here’s what the Product Backlog looks like for the same team project:

The product backlog columns

As you can see from the screenshot, the Product Backlogs columns match up to the output values, with a single difference: the work item field by which the work items are ordered is inserted at the first position. This is because the work items in this backlog are sorted in ascending order by this field. This work item field is determined by the value of the Order type field, and is read from the TypeFields property of the CommonProjectConfiguration object.

So far, so good. Let’s see what the Iteration Backlog looks like:

The iteration backlog columns

Huh, things seem a bit out of place here. I’m not completely sure how the entire layout is related to the configuration settings. It appears that the System.Id field is always pushed to the first position. Then, the Microsoft.VSTS.Scheduling.Effort field is not included for some reason. My guess is that WebAccess knows that that work item field matches the Effort type field and it removes it from the UI, although using it to calculate the required statistics. Don’t take my word for it, though. Then, we have the Title, State, AssignedTo and finally the RemainingWork fields, as specified in the settings, although the WorkItemType field is oddly squeezed in between them. Lastly, the Activity type field is added. If anyone knows the exact algorithm these fields are added to the UI, I’d be very interested to hear about it!

The quick-add work item panel

The quick-add work item panel I’m referring to is the small panel you can have displayed above your Product Backlog grid, and you can use it to quickly add a Requirement work item to the backlog. Here’s what it looks like:

The add items panel

And here’s a snippet to get these settings:

void ReadQuickAddPanelFields(ProjectProcessConfigurationService cfgSvc)
{
    var agileCfg = cfgSvc.GetAgileConfiguration(_projectUri);
    AddPanelConfiguration addPanel = agileCfg.ProductBacklog.AddPanel

    if (addPanel.Fields.Any())
    {
        foreach (var field in addPanel.Fields)
            Console.WriteLine(field.Name);
    }
    else
    {
        Console.WriteLine("No fields to display.");
    }
}

And finally, the output for the Product Backlog that matches the previous screenshot:

System.Title

Wow, now that was exciting! As you can see, the work item type field is required and it’s always the first one on the list. Therefore, it’s not included in the settings. The other fields you do specify in the settings object would appear one below the other in the order you specify them. Simple, right?

Conclusion

This is the end of the (hopefully not too long) blog post. The source code that demonstrates the features outlined in the post can be downloaded here. Just as a reminder, you will need to have Visual Studio 11 beta installed in order to successfully compile the solution. Please feel free to leave any comments you may have. Or just say hi, that’s totally fine too!

Be Sociable, Share!
  • Biju

    thanks a lot for this blog

  • passing by

    nice

  • Ziby

    The “AgileProjectConfiguration” in the AgileConfiguration.xml (same folder as CommonConfiguration.xml in the ProcessTemplate) governs the fields that appear on UI for ProductBacklog, AddPanel, and IterationBacklog.