Click here to Skip to main content
15,881,882 members
Articles / DevOps / TFS

TFS API: TFS Work Item All Changes History

Rate me:
Please Sign up or sign in to vote.
2.50/5 (2 votes)
30 Aug 2016CPOL3 min read 32.3K   539   6   2
TFS WorkItem History

Introduction

WorkItem history plays a key role when it comes to project tracking and auditing. It allows us to see what all activities that happened on workitem, who changed it, when and what. In Visual Studio TFS UI, we can see these details under 'History->All changes' tab.

Same data, we can have using TFS API and moreover, UNLIKE Visual Studio TFS, we can plot data in tabular format which is sortable, exported in Excel and can be saved on local machine for future reference.

This article will explain how this can be done programmatically in the most simple way.

Highlights of this application.

  • Brings all history changes for workitem including field changes, work item link added/deleted, hyperlinks, attachments and history comments.
  • History is displayed in grid format having information of changed by, changed date, field name, old value, new value and comments.
  • Using export to Excel feature work item changes can be exported to Excel and saved locally.

Having these data in tabular format in local Excel, gives full control and flexibility to use it in a way we want.

Using the Code

Let's see this step by step.

Step 1: Get Connected to TFS

Connect to TFS using TFS Uri and have WorkItemStore object.

C#
private TfsTeamProjectCollection projectCollection;
private WorkItemStore workItemStore;
private WorkItemStore service;

private void ConnectToTFS()
        {
            try
            {
                projectCollection =
                TfsTeamProjectCollectionFactory.GetTeamProjectCollection(
                    new Uri(txtTFSURL.Text));
                service = projectCollection.GetService<WorkItemStore>();
                if (service != null)
                {
                    workItemStore = projectCollection.GetService<WorkItemStore>();
                    MessageBox.Show("TFS connected successfully");
                }               
            }
            catch (Exception Ex)
            {
                MessageBox.Show(Ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
              
            }
        }

Step 2: Get WorkItem

After connecting to TFS successfully, get WorkItem object using entered workItem Id.

C#
workItem = workItemStore.GetWorkItem(Convert.ToInt32(txtWorkItemId.Text.Trim()));

This code will return object of WorkItem and that is where all information of WorkItem resides. One has to play with this object and extract the required information. Here, we are interested in properties 'Revisions', 'Links', 'Attachments' and 'WorkItemLinkHistory'.

'Revisions' property is a collection of 'Revision' object of workitem having revision number from 1 to so on.... Code iteraters through each revision. 'Revision' object has collection of Fields that hold value of each WorkItem field in that revision.

In the next steps, we will see how we can use these properties and get all changes that happened to workitem.

Step 3: Iterate through Revisions and Fields

To get changes, we need to compare revisions and see which field values are changed. In this looping code, we will get value of field from the previous revision and compare with current revision. If value is changed, row will be added to grid having details of who changed it, when it was changed, what was changed.

C#
string strOldValue, strNewValue; 
for (int i = 1; i < workItem.Revisions.Count; i++)
            {
                foreach (Field field in workItem.Revisions[i].Fields)
                {
                    if (field.Name != "Rev" && field.Name != "Changed By" && 
                        field.Name != "Revised Date" && field.Name != "Watermark" && 
                        field.Name != "Changed Date" &&
                        field.Name != "Hyperlink Count" && field.Name != "Related Link Count" && 
                        field.Name != "Attached File Count" && 
                        field.Name != "External Link Count") //Skip some Obvious fields
                    {                        
                            strOldValue = 
                            (workItem.Revisions[i - 1].Fields[field.Name].Value ?? "").ToString();
                            strNewValue = (field.Value ?? "").ToString();
                            if (strOldValue != strNewValue)
                            {
                                gvAllChanges.Rows.Add
                                (workItem.Revisions[i].Fields["Changed By"].Value.ToString(), 
                                Convert.ToDateTime(workItem.Revisions[i].Fields["Changed Date"].Value), 
                                field.Name, strOldValue, strNewValue, "");
                            }                        
                    }
                }
            }

Step 4: Get Hyperlinks,Externallinks and Attachments

To get Hyperlinks and ExternalLinks (i.e., links to TestResults in MTM), we need to filter out WorkItem.Links collection using 'BaseType' property. All attachments can be retrieved from Workttem.Attachments collection. These collections give a list of links/attachments but don't say when and who created them. For that, we need to look into each revision and compare fields 'Hyperlink Count', 'External Link Count' and 'Attached File Count' respectively. If count is changed, then we have to get those many links from collection.

The below code explains this.

Hyperlinks

C#
if (workItem.HyperLinkCount > 0)
               {
                   foreach (Link link in workItem.Links)
                   {
                       if (link.BaseType.ToString() == "Hyperlink")
                       {
                           hyperLinkColl.Add((Hyperlink)link);

                       }
                   }
               }

In revisions loop:

C#
prevCount = Convert.ToInt32(workItem.Revisions[i - 1].Fields["Hyperlink Count"].Value);
currCount = Convert.ToInt32(workItem.Revisions[i].Fields["Hyperlink Count"].Value);

if (currCount > prevCount)
{
     for (int j = prevCount; j < (hyperLinkColl.Count - (hyperLinkColl.Count - currCount)); j++)
     {
          gvAllChanges.Rows.Add(workItem.Revisions[i].Fields["Changed By"].Value.ToString(), 
          Convert.ToDateTime(workItem.Revisions[i].Fields["Changed Date"].Value), 
          "Hyperlink", "", hyperLinkColl[j].Location, hyperLinkColl[j].Comment);
     }
}

External Links

C#
if (workItem.ExternalLinkCount > 0)
               {
                   foreach (Link link in workItem.Links)
                   {
                       if (link.BaseType.ToString() == "ExternalLink")
                           externalLinkColl.Add((ExternalLink)link);
                   }
               }

In revisions loop:

C#
prevCount = Convert.ToInt32(workItem.Revisions[i - 1].Fields["External Link Count"].Value);
currCount = Convert.ToInt32(workItem.Revisions[i].Fields["External Link Count"].Value);

if (currCount > prevCount)
{
      for (int j = prevCount;
           j < (externalLinkColl.Count - (externalLinkColl.Count - currCount)); j++)
      {
           gvAllChanges.Rows.Add(workItem.Revisions[i].Fields["Changed By"].Value.ToString(),
           Convert.ToDateTime(workItem.Revisions[i].Fields["Changed Date"].Value),
           "External Link", "", externalLinkColl[j].ArtifactLinkType.Name + ":" +
           externalLinkColl[j].LinkedArtifactUri, externalLinkColl[j].Comment);
      }
}

Attachments

C#
int prevCount = Convert.ToInt32(workItem.Revisions[i - 1].Fields["Attached File Count"].Value);
int currCount = Convert.ToInt32(workItem.Revisions[i].Fields["Attached File Count"].Value);
if (currCount > prevCount)
{
     AttachmentCollection attachementColl = workItem.Attachments;
     for (int j = prevCount; j < (attachementColl.Count - (attachementColl.Count - currCount)); j++)
     {
          gvAllChanges.Rows.Add(workItem.Revisions[i].Fields["Changed By"].Value.ToString(),
          Convert.ToDateTime(attachementColl[j].AttachedTime), "Attachment", "",
          attachementColl[j].Name, attachementColl[j].Comment);
     }
}

Step 5: Get WorkItem Links

'WorkItem.WorkItemLinkHistory' collection holds list of WorkItem links including Deleted Links. This is the main reason why we are using this collection instead of 'WorkItem.Links' collection.

To identify deleted links, look for 'RemovedBy' property. If it is not empty or not set as default, then those links are deleted. Fetch all the required information of that link and include it in your history grid list.

C#
foreach (WorkItemLink link in workItem.WorkItemLinkHistory)
{
     //Added Links
     gvAllChanges.Rows.Add(link.AddedBy, Convert.ToDateTime(link.AddedDate), 
     "WorkItem Link Added", "", "WorkItem " + 
     link.TargetId + " (" + link.LinkTypeEnd.Name + ")", 
     link.Comment);
     //Deleted Links
     if (link.RemovedBy != "" && link.RemovedBy != "TFS Everyone")
            gvAllChanges.Rows.Add(link.RemovedBy, Convert.ToDateTime(link.RemovedDate), 
            "WorkItem Link Deleted", "", "WorkItem " + link.TargetId + 
            " (" + link.LinkTypeEnd.Name + ")", link.Comment);
} 

Step 6: Have Your Entire History Visualized

Since we have collected all history changes, let's put them together arranging date wise.

C#
gvAllChanges.Sort(this.gvAllChanges.Columns["ChangedDateAllChanges"], ListSortDirection.Descending);

Now, you will able to see all changes as latest on top. Sample, outlook looks like below:

Image 1

You, can export this grid in format you want and save it locally. I have exported it in Excel using Interop services (Browse Code for more details).

WorkItem history saved with different timestamp is a BIG help in Auditing and Tracking activities...

Have a happy TFS API programming !!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiondll's missing Pin
Surya VSN1-Aug-23 20:39
Surya VSN1-Aug-23 20:39 
Questionhow to execute this scrip Pin
Member 1377556611-Apr-18 22:58
Member 1377556611-Apr-18 22:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.