Tag Archives: GetTeamProjectCollection

Programmatically List Existing Tags Using The TFS API

Having looked into this for some time; I came up with the following method of extracting team project tags. I’m not for a minute suggesting this is the best way of doing this – but it does work. My guess is that it’s not a very scalable solution, as it’s doing a LOT of work.

As it was, I couldn’t find a way to directly query the tags, so instead, I’m going through all the work items, and picking the tags. I couldn’t even find a way to filter the work items that actually have tags; so here’s the query that I ended up with:

private static IEnumerable<string> GetAllDistinctWorkItemTags(string uri, string projectName)
{
    TfsTeamProjectCollection tfs;
 
    tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(uri)); // https://mytfs.visualstudio.com/DefaultCollection
    tfs.Authenticate();
 
    var wis = new WorkItemStore(tfs);
 
    WorkItemCollection workItemCollection = wis.Query(
         " SELECT [System.Tags]" +
         " FROM WorkItems " +
         $" WHERE [System.TeamProject] = '{projectName}' ");                
 
    if (workItemCollection.Count == 0)
        return null;
 
    List<string> tags = new List<string>();
    foreach (WorkItem wi in workItemCollection)
    {
        if (string.IsNullOrWhiteSpace(wi.Tags)) continue;
 
        var splitTags = wi.Tags.Split(';');
        tags.AddRange(splitTags.ToList());                
    }
 
    return tags.Distinct();
}

From debugging, I strongly suspect that whatever you put in the “SELECT”, it returns the entire work item. I also, for the life of me, couldn’t work out a lambda query for parsing the tags.

The calling method is here:

public static IEnumerable<string> GetAllTags(string uri, string teamProject)
{
    var project = GetTeamProject(uri, teamProject);
    IEnumerable<string> tags = GetAllDistinctWorkItemTags(uri, teamProject);
 
    return tags;
}

I’ve listed GetTeamProject helper method before, but for the sake of completeness:


public static Project GetTeamProject(string uri, string name)
{
    TfsTeamProjectCollection tfs;
 
    tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(uri)); // https://mytfs.visualstudio.com/DefaultCollection
    tfs.Authenticate();
 
    var workItemStore = new WorkItemStore(tfs);
    
    var project = (from Project pr in workItemStore.Projects
                       where pr.Name == name
                       select pr).FirstOrDefault();
    if (project == null)
        throw new Exception($"Unable to find {name} in {uri}");
 
    return project;
}

Here’s the output:

tags1

Notes on Tags

A couple of points on tags: firstly, tags seem to exist in a kind of transient state; that is, while something is tagged, the tag exists, but once you remove all instances of a tag (for example, if I removed “Tagtest1” from all work items in my team project, TFS would eventually (I believe after a couple of days) just delete the tag for me. Obviously, in my example, as soon as I did this, I would no longer find it. This might leave you thinking that there is a more efficient way of removing tags (that is, you should be able to access the transient store in some way).

The existence of this Visual Studio plug-in lends support to that idea. It allows you to maintain the tags within your team project. If you’re using tags in any kind of serious way then I’d strongly recommend that you try it.

Performance

This is doing a lot of (IMO) unnecessary work, so I tried a little performance test; using this post as a template, I created a lot of bugs:

tags2

As you can see, I created a random set of tags. One other point that I’m going to put here is that a TFS database with ~30K work items and no code whatsoever increases the size of the default collection DB to around 2GB:

tags3

Now I ran the GetAllTags with some timing on:

tags4

19 seconds, which seems like quite a reasonable speed to me for 13.5k tags.

Programmatically creating a test case for a work item using the TFS API

Previously I’ve covered how to programmatically create a bug in TFS. In this post, we’ll create a test case to cover it.

Set-up

What we’re going to do here is to create a new test case based on the bug that we created in the linked post, then we’re going to copy key values across, and link the two.

First, we need to do some re-factoring; the section of code the saves the work item can be extracted into something like this:

private static ActionResult CheckValidationResult(WorkItem workItem)
{
    var validationResult = workItem.Validate();
 
    ActionResult result = null;
    if (validationResult.Count == 0)
    {
        // Save the new work item.
        workItem.Save();
 
        result = new ActionResult()
        {
            Success = true,
            Id = workItem.Id
        };
    }
    else
    {
        result = ParseValidation(validationResult);
    }
 
    return result;
}

ActionResult is here for reference:


public class ActionResult
{
    public bool Success { get; set; }
    public List<string> ErrorCodes { get; set; }
    public int Id { get; set; }
}

Let’s re-visit what the code to create the bug looks like now:


public static ActionResult CreateNewBug(string uri, string teamProject, string title, string description,
                string area, string iteration, string assignee, string reproductionSteps)
{
    var project = TFSUtilLibrary.TeamProjectHelper.GetTeamProject(uri, teamProject);
    return CreateNewBug(project, title, description, area, iteration, assignee, reproductionSteps);
}
private static ActionResult CreateNewBug(Project teamProject, string title, string description, 
    string area, string iteration, string assignee, string reproductionSteps)
{
    WorkItemType workItemType = teamProject.WorkItemTypes["Bug"];
 
    // Create the work item. 
    WorkItem newBug = new WorkItem(workItemType);
    newBug.Title = title;
    newBug.Description = description;
    newBug.AreaPath = area;
    newBug.IterationPath = iteration;
    newBug.Fields["Assigned To"].Value = assignee;
 
    newBug.Fields["Repro Steps"].Value = reproductionSteps;
 
    newBug.Fields["Tags"].Value = "Tagtest1, tagtest2, tagtest3";
 
    ActionResult result = CheckValidationResult(newBug);
 
    return result;
}

You’ll notice that I’ve added some tags. Why will become apparent later.

Finding a Work Item

We’re creating a test case for a work item; consequently, we need to be able to retrieve a WorkItem, given an ID:


private static WorkItem GetWorkItem(string uri, int testedWorkItemId)
{
    TfsTeamProjectCollection tfs;
 
    tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(uri)); // https://mytfs.visualstudio.com/DefaultCollection
    tfs.Authenticate();
 
    var workItemStore = new WorkItemStore(tfs);
    WorkItem workItem = workItemStore.GetWorkItem(testedWorkItemId);
 
    return workItem;
 
}

Get a Team Project

The next step is that we’ll need to be able to find a team project (we just will):


public static Project GetTeamProject(string uri, string name)
{
    TfsTeamProjectCollection tfs;
 
    tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(uri)); // https://mytfs.visualstudio.com/DefaultCollection
    tfs.Authenticate();
 
    var workItemStore = new WorkItemStore(tfs);
    
    var project = (from Project pr in workItemStore.Projects
                       where pr.Name == name
                       select pr).FirstOrDefault();
    if (project == null)
        throw new Exception($"Unable to find {name} in {uri}");
 
    return project;
}

Create the Test Case

We now have everything that we need to create the test case. Here’s the parent code that will create the bug:


var result = WorkItemHelper.CreateNewBug(TFSUri, "TFSSandbox",
    "Test new bug", "New bug description", @"TFSSandbox\Team 12", @"TFSSandbox\Iteration 1", "Paul Michaels",
    "Click the screen");
 
if (result.Success)
{
    // Now create the test case
    var resultTestCase = WorkItemHelper.CreateNewTestCase(
        TFSUri, "TFSSandbox", result.Id, "Test case description", "Paul Michaels",
        "reproduction steps here");
}

So, we’re creating a bug and, if it’s successful, we’re creating a test case for it; the CreateNewTestCase code looks like this:


public static ActionResult CreateNewTestCase(string uri, string teamProject, 
        int testedWorkItemId, string description, string assignee, string reproductionSteps)
{
    var project = GetTeamProject(uri, teamProject);
    var workItem = GetWorkItem(uri, testedWorkItemId);
    return CreateNewTestCase(uri, project, workItem, description, assignee, reproductionSteps);
}

private static ActionResult CreateNewTestCase(string uri, Project project, WorkItem testedWorkItem, string description, string assignee, string reproductionSteps)
{
    WorkItemType workItemType = project.WorkItemTypes["Test Case"];
 
    // Create the work item. 
    WorkItem newTestCase = new WorkItem(workItemType);
    newTestCase.Title = $"Test {testedWorkItem.Title}";
    newTestCase.Description = description;
    newTestCase.AreaPath = testedWorkItem.AreaPath;
    newTestCase.IterationPath = testedWorkItem.IterationPath;
    newTestCase.Fields["Assigned To"].Value = assignee;
  
    // Copy tags
    newTestCase.Fields["Tags"].Value = testedWorkItem.Fields["Tags"].Value;
 
    ActionResult result = CheckValidationResult(newTestCase);
    if (result.Success)
    {
        CreateTestedByLink(uri, testedWorkItem, result.Id);
    }
 
    return result;
}

Couple of things to note here; the first is the tags – we’re copying them from the bug (see references). The second is that we are linking the two.

Links

Here’s how I created the link:


private static void CreateTestedByLink(string uri, WorkItem testedWorkItem, int newTestCaseId)
{
    TfsTeamProjectCollection tfs;
 
    tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(uri)); // https://mytfs.visualstudio.com/DefaultCollection
    tfs.Authenticate();
 
    var workItemStore = new WorkItemStore(tfs);
 
    var linkTypes = workItemStore.WorkItemLinkTypes;
 
    WorkItemLinkType testedBy = linkTypes.FirstOrDefault(lt => lt.ForwardEnd.Name == "Tested By");
    WorkItemLinkTypeEnd linkTypeEnd = testedBy.ForwardEnd;
 
    //Add the link as related link.
    testedWorkItem.Links.Add(new RelatedLink(linkTypeEnd, newTestCaseId));
    var result = CheckValidationResult(testedWorkItem);
}

It feels like there might be a slicker way than referencing “Tested By” by name, but this is the only way I could find.

Here’s the created bug with a linked test case:

tfsapi_bug_test

Conclusion

As with the previous post, I’m not trying to re-write TeamCity or anything here; this was just born out of some pain with manually setting these things up.

You’ll also notice that I’ve left the Test Steps; I’ll come back to them shortly (and by ‘shortly’, I mean in a later post).

References

http://blogs.microsoft.co.il/shair/2010/02/27/tfs-api-part-22-create-link-between-work-item-parent-child-etc/

https://social.msdn.microsoft.com/Forums/vstudio/en-US/8d8bfc70-4da7-40ac-ad34-28ab8ef73314/add-tags-programmatically-to-work-items?forum=tfsgeneral

Unable to get IBuildServer – always returning null

I recently had a very strange error while using the TFS API. The error was with the following code:

var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(URI));
var vcs = tfs.GetService<VersionControlServer>();
var buildServer = tfs.GetService<IBuildServer>();                

The problem being that `buildServer` was being set to null. I had the TFS server set-up locally, and there was a controller, an agent and a build configured.

I spent a good while trying to debug, what I thought was, an error with the code.

The Solution

Turns out the problem was with the reference:

TFSBuildRef

The problem was that I was referencing the wrong version (trying to connect to TFS2013 but the build client version was 11.0.0.0). Swapping it to version 12.0.0.0 works fine.

It sounds like a silly mistake, but if you make it, it’s very difficult to spot.