在TFS中启动Sprint后跟踪故事

时间:2014-02-18 23:47:46

标签: tfs tfs2012 scrum

我正在使用TFS 2012和Scrum流程模板。

我想要一种方法来查看在该sprint的时间轴中某个点之后添加到sprint的故事。

我们通常会进行一次中短距离“迷你”计划会议。这使我们能够重新平衡整个团队中的现有工作负载,但如果我们看到我们完成了比预期更多的工作,它还允许我们向Sprint添加额外的PBI /用户故事。

我无法查询查询故事的迭代路径何时更改的方法。这可能吗?

2 个答案:

答案 0 :(得分:2)

我发现有必要使用TFS API创建自定义应用程序来完成此任务。

首先,您需要获取相关的工作项列表:

    var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(options.TfsUri));

    var workItemStore = tfs.GetService<WorkItemStore>();
    var project = workItemStore.Projects[options.ProjectName]; 
    var projectIterationPath = string.Format("{0}\\{1}", project.Name, options.IterationPath);

    var items = project.Store.Query(
            "SELECT [System.Id] FROM WorkItems WHERE [System.WorkItemType] IN ('User Story', 'Product Backlog Item', 'Bug') AND [System.IterationPath] = '" +
            projectIterationPath + "'");

然后你必须总结各种总分(总工作与完成的工作):

    var pointTotal = workItems
        .Where(
            i =>
                i.Fields[effortField].Value != null &&
                decimal.TryParse(i.Fields[effortField].Value.ToString(), out temp))
        .Sum(i => decimal.Parse(i[effortField].ToString()));

    var pointsCompleted = workItems
         .Where(
             i =>
                 (string.Compare(i.State, "done", true) == 0 || string.Compare(i.State, "closed", true) == 0) &&
                 i.Fields[effortField].Value != null &&
                 decimal.TryParse(i.Fields[effortField].Value.ToString(), out temp))
         .Sum(i => decimal.Parse(i[effortField].ToString()));

然后,您需要获取有关开始和结束迭代日期的信息:

    var iterationSchedule = GetIterationSchedule(tfs, project.Uri.ToString(), 

    private static ScheduleInfo GetIterationSchedule(TfsTeamProjectCollection tfs, string projectUri, string iterationPath)
    {
        var css = tfs.GetService<ICommonStructureService4>();
        var structures = css.ListStructures(projectUri);
        var iterations = structures.FirstOrDefault(s => s.StructureType.Equals("ProjectLifecycle"));

        if (iterations != null)
        {
            string projectName = css.GetProject(projectUri).Name;

            XmlElement iterationsTree = css.GetNodesXml(new[] {iterations.Uri}, true);
            return GetIterationDates(iterationsTree.ChildNodes[0], projectName, iterationPath);
        }

        return null;
    }

    private static ScheduleInfo GetIterationDates(XmlNode node, string projectName, string iterationPath)
    {
        var targetIterationPath = string.Format("\\{0}\\Iteration\\{1}", projectName, iterationPath);
        XElement targetIteration = null;

        if (node != null)
        {
            var iterations = XDocument.Parse(node.InnerXml);

            targetIteration = iterations.Descendants("Node")
                .Where(n => n.Attribute("Path") != null && !string.IsNullOrEmpty(n.Attribute("Path").Value))
                .SingleOrDefault(n => string.Compare(n.Attribute("Path").Value, targetIterationPath, true) == 0);
        }

        if (targetIteration != null)
        {
            // Attempt to read the start and end dates if they exist.
            string strStartDate = (targetIteration.Attribute("StartDate") != null)
                ? targetIteration.Attribute("StartDate").Value
                : null;
            string strEndDate = (targetIteration.Attribute("FinishDate") != null)
                ? targetIteration.Attribute("FinishDate").Value
                : null;

            DateTime? rStateDate = null, rEndDate = null;

            if (!string.IsNullOrEmpty(strStartDate) && !string.IsNullOrEmpty(strEndDate))
            {
                DateTime startDate;
                if (DateTime.TryParse(strStartDate, out startDate))
                    rStateDate = startDate;

                DateTime endDate;
                if (DateTime.TryParse(strEndDate, out endDate))
                    rEndDate = endDate;
            }

            return new ScheduleInfo
            {
                IterationPath = iterationPath,
                StartDate = rStateDate,
                EndDate = rEndDate
            };
        }

        return null;
    }

最后你将它们拼凑在一起以生成冲刺的摘要:

        foreach (var item in workItems)
        {
            var postSprintStartRevisions = (from r in item.Revisions.Cast<Revision>()
                where r.Fields.Cast<Field>()
                        .Any(f => f.Name == "Revised Date" && ((DateTime) f.Value) >= postStartDate)
                    && r.Fields.Cast<Field>()
                        .Any(
                            f =>
                                f.Name == "Iteration Path" 
                                && string.Compare(f.OriginalValue.ToString(), projectIterationPath, true) != 0
                                && string.Compare(f.Value.ToString(), projectIterationPath, true) == 0)
                select r).ToArray();

            if (postSprintStartRevisions.Any() && item[effortField] != null)
            {
                pointsPostSprintStart += decimal.Parse(item[effortField].ToString());
            }
        }

代码并不漂亮,但它确实让我想要我。

用法:

Usage: pbitracker -TfsUri [uri] -ProjectName [name] -IterationPath [path] -b 3

  -u, --TfsUri             Required. TFS Project Collection Uri. Ex:
                           http://tfs:8080/tfs/defaultcollection

  -p, --ProjectName        Required. TFS Project Name. Ex: MyProject

  -i, --IterationPath      Required. Iteration Path. Ex: 'GA\Sprint 3'

  -b, --StartDateBuffer    (Default: 3) Number of days after the start date to
                           consider the worked as added post-start

  --help                   Display this help screen.

我认为“添加后启动”是基于传入应用程序的选项。我将其默认为3天,所以如果您的sprint在星期一开始并且您在周四添加了工作,则该工作被视为在开始后添加的点数。我会尝试在GitHub或CodePlex上解决这个问题,我会更新答案。

示例输出:

Querying with the following options...
TFS Uri:                http://tfs:8080/tfs/DefaultCollection
Project Name:           MyProject
Iteration Path:         GA\Sprint 16
Please wait...


Iteration Start Date:                   2/24/2014 12:00:00 AM
Iteration End Date:                     3/7/2014 12:00:00 AM
Iteration Point Total:                  168
Iteration Point Total Completed:        168
Iteration Points Starting Points:       162
Iteration Points Added Post-Start:      6

答案 1 :(得分:-1)

我认为有很多方法可以做到这一点。但我所做的是创建一个简单的查询。在下面的屏幕截图中,我使用了TFS2012 ALM VM和FabrikamFiber示例。您可以看到查询和结果窗格的构造。

最后一项显示了在sprint开始后我进入sprint的产品积压项目,我认为这就是你所要求的。您可以通过“更改日期”值晚于冲刺开始日期来看到此信息。

enter image description here

此后,将该查询添加到收藏夹是一项简单的任务。不要忘记更改迭代路径!