65.9K
CodeProject 正在变化。 阅读更多。
Home

TFS API:TFS 工作项所有更改历史记录

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2016年8月26日

CPOL

3分钟阅读

viewsIcon

34513

downloadIcon

588

TFS WorkItem 历史记录。

引言

在项目跟踪和审计方面,工作项历史记录起着关键作用。 它允许我们查看工作项上发生的所有活动、谁更改了它、何时更改以及更改了什么。 在 Visual Studio TFS UI 中,我们可以在“历史记录->所有更改”选项卡下看到这些详细信息。

我们可以使用 TFS API 获取相同的数据,而且,与 Visual Studio TFS 不同,我们可以以表格格式绘制数据,该格式可排序、可导出到 Excel 并且可以保存在本地计算机上以供将来参考

本文将解释如何以最简单的方式以编程方式完成此操作。

此应用程序的亮点。

  • 获取工作项的所有历史更改,包括字段更改、添加/删除的工作项链接、超链接、附件和历史记录注释。
  • 历史记录以网格格式显示,包含更改者、更改日期、字段名称、旧值、新值和注释的信息。
  • 使用导出到 Excel 功能,可以将工作项更改导出到 Excel 并保存在本地。

在本地 Excel 中以表格格式保存这些数据,可以完全控制和灵活地以我们想要的方式使用它。

Using the Code

让我们逐步看看。

步骤 1:连接到 TFS

使用 TFS Uri 连接到 TFS 并获取 WorkItemStore 对象。

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);
              
            }
        }

步骤 2:获取工作项

成功连接到 TFS 后,使用输入的 workItem Id 获取 WorkItem 对象。

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

此代码将返回 WorkItem 的对象,这就是 WorkItem 的所有信息所在的位置。 必须使用此对象并提取所需的信息。 在这里,我们对属性“Revisions”、“Links”、“Attachments”和“WorkItemLinkHistory”感兴趣。

Revisions”属性是 workitem 的“Revision”对象的集合,其修订号从 1 到 ... 等等。 代码遍历每个修订。 “Revision”对象具有 Fields 的集合,该集合保存该修订中每个 WorkItem 字段的值。

在接下来的步骤中,我们将看到如何使用这些属性并获取对 workitem 所做的所有更改。

步骤 3:遍历修订和字段

要获取更改,我们需要比较修订,看看哪些字段值发生了更改。 在此循环代码中,我们将从上一个修订中获取字段的值,并与当前修订进行比较。 如果值发生了更改,则会将行添加到网格中,其中包含有关谁更改了它、何时更改了它以及更改了什么的详细信息。

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, "");
                            }                        
                    }
                }
            }

步骤 4:获取超链接、外部链接和附件

要获取超链接和外部链接(即指向 MTM 中 TestResults 的链接),我们需要使用“BaseType”属性过滤 WorkItem.Links 集合。 所有附件都可以从 Workttem.Attachments 集合中检索。 这些集合给出了链接/附件的列表,但没有说明何时以及谁创建了它们。 为此,我们需要查看每个修订并比较字段“Hyperlink Count”、“External Link Count”和“Attached File Count”分别。 如果计数发生了变化,那么我们必须从集合中获取这些链接。

以下代码对此进行了解释。

超链接

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

                        }
                    }
                }

在修订循环中

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);
     }
}

外部链接

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

在修订循环中

 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);
       }
 }

附件

 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);
      }
 }

步骤 5:获取工作项链接

WorkItem.WorkItemLinkHistory”集合保存了 WorkItem 链接的列表,包括已删除的链接。 这是我们使用此集合而不是“WorkItem.Links”集合的主要原因。

要识别已删除的链接,请查找“RemovedBy”属性。 如果它不为空或未设置为默认值,则这些链接将被删除。 获取该链接的所有必需信息,并将其包含在您的历史记录网格列表中。

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);
} 

步骤 6:可视化您的整个历史记录

既然我们已经收集了所有历史记录更改,让我们将它们放在一起按日期排列。

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

现在,您将能够看到所有更改,最新的在顶部。 示例,outlook 如下所示

您可以以您想要的格式导出此网格并将其保存在本地。 我已经使用 Interop 服务将其导出到 Excel(浏览代码以获取更多详细信息)。

以不同时间戳保存的工作项历史记录对于审计和跟踪活动来说是一个巨大的帮助...

祝您 TFS API 编程愉快!!

© . All rights reserved.