TFS Timesheets

TFS Timesheets

В данном посте я хочу затронуть тему получения отчетов о затраченном времени из TFS.

При работе с TFS мы вручную увеличиваем значения поля Completed work на то количество часов, сколько было на него потрачено. Мы не указываем то, когда были потрачены эти N часов. Считается, что разработчики делают это по факту выполнения работы.

Что же делать, если мы хотим получить сводную таблицу с указанием сколько, куда и когда были потрачены эти часы? К сожалению, из коробки, такой возможности нет. Однако есть определенный набор API, который поможет нам написать утилиту, создающую желаемый отчет. И называется оно TFS Object Model. Именно этим мы и займемся. Напишем ее.

Итак, наше приложение будет консольным.

Пример его использования описан ниже:

TFS_Timesheet.exe /collection:http://server:8080/tfs/defaultcollection /from:2012-03-01 /to:2012-03-31

В качестве параметров передаются адрес TFSколлекции проектов и даты начала и конца для формирования отчета.

Создадим класс для хранения этих параметров:

public class RequestData

{

    public RequestData()

    {

        From = new DateTime(1900, 1, 1);

        To = new DateTime(2500, 1, 1);

    }

 

    public Uri Collection { get; set; }

    public DateTime From { get; set; }

    public DateTime To { get; set; }

}

При старте приложения инициализируем наш класс:

var request = new RequestData();

 

foreach (var arg in args)

{

    if (arg.StartsWith("/collection:"))

    {

        request.Collection = new Uri(arg.Substring("/collection:".Length));

    }

    else if (arg.StartsWith("/from:"))

    {

        request.From = DateTime.Parse(arg.Substring("/from:".Length) + "T00:00:00.0000000");

    }

    else if (arg.StartsWith("/to:"))

    {

        request.To = DateTime.Parse(arg.Substring("/to:".Length) + "T00:00:00.0000000");

    }

}

Чтение данных из TFS

Добавим еще один класс, который будет содержать в себе данные о выполнении некоторой работы.

public class TimeEntry

{

    public string Name { get; set; }

    public DateTime Date { get; set; }

    public int Id { get; set; }

    public string Title { get; set; }

    public double Hours { get; set; }

    public string Comments { get; set; }

}

Теперь нашей задачей является получение коллекции объектов этого класса. Т.е. получение списка проделанной работы за указанный период времени.

var timeEntries = new List<TimeEntry>();

 

var teamProjectCollection = new TfsTeamProjectCollection(request.Collection, true);

 

var workItemStore = teamProjectCollection.GetService<WorkItemStore>();

var workItems = workItemStore.Query(string.Format(

    @"SELECT [System.Id], [System.ChangedDate], [System.Title], [System.State]

      FROM WorkItems

      WHERE [System.ChangedDate] >= '{0}' AND [System.ChangedDate] < '{1}'

      ORDER BY [System.ChangedDate]", request.From.ToLongDateString(), request.To.AddDays(1).ToLongDateString()));

foreach (WorkItem workItem in workItems)

{

    if (workItem == null || !workItem.IsValid()) continue;

 

    foreach (Revision rev in workItem.Revisions)

    {

        foreach (Field field in rev.Fields)

        {

            if (field.Name == "Completed Work" &&

                field.OriginalValue != field.Value &&

                Convert.ToDateTime(rev.Fields[CoreField.ChangedDate].Value) >= request.From &&

                Convert.ToDateTime(rev.Fields[CoreField.ChangedDate].Value) < request.To.AddDays(1))

            {

                var entry = new TimeEntry();

                entry.Id = workItem.Id;

                entry.Date = Convert.ToDateTime(rev.Fields[CoreField.ChangedDate].Value);

                entry.Name = rev.Fields[CoreField.ChangedBy].Value.ToString();

                entry.Title = workItem.Title;

                entry.Hours = Convert.ToDouble(field.Value) - Convert.ToDouble(field.OriginalValue);

                if (entry.Hours > 0)

                {

                    timeEntries.Add(entry);

                }

            }

        }

    }

}

Формирование отчета

Завершающим этапом служит построение отчета на основании собранных данных.

private static void WriteTimesheet(List<TimeEntry> timeEntries)

{

    var workItems = timeEntries.Select(t => t.Id).Distinct().OrderBy(t => t).ToList();

    var dates = timeEntries.Select(t => t.Date.Date).Distinct().OrderBy(t => t).ToList();

 

    using(var timesheet = File.CreateText("timesheet_report.html"))

    {

        timesheet.Write("<html><head><title>TFS Timesheet Report</title><style>\n"+Properties.Resources.Css+"\n</style>");

        timesheet.Write("<meta content=\"text/html; charset=utf-8\" http-equiv=\"content-type\" />");

        timesheet.Write("<head><body><table><thead><tr><th class='id'>ID</th><th class='title'>Title</th><th>&nbsp;</th>");

 

        foreach (var date in dates)

        {

            timesheet.Write("<th>" + date.ToShortDateString() + "</th>");

        }

        timesheet.WriteLine("</tr></thead><tbody>");

 

        // Общеевремя.

        timesheet.Write("<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>");

        foreach (var date in dates)

        {

            var entries = timeEntries.Where(t => t.Date.Date == date).ToList();

            var hours = entries.Aggregate(0.0, (t, i) => t + i.Hours);

            timesheet.Write("<td class='total'>" + hours + "</td>");

        }

        timesheet.Write("</tr>");

 

        // Распределение времени по задачам.

        foreach (var wid in workItems)

        {

            timesheet.Write("<tr>");

            timesheet.Write("<td class='id'>" + wid + "</td>");

            var title = timeEntries.First(t => t.Id == wid).Title;

            timesheet.Write("<td class='title'>" + title + "</td>");

 

            var entries = timeEntries.Where(t => t.Id == wid).ToList();

            var hours = entries.Aggregate(0.0, (t, i) => t + i.Hours);

            timesheet.Write("<td class='total'>" + hours + "</td>");

 

            foreach (var date in dates)

            {

                entries = timeEntries.Where(t => t.Id == wid && t.Date.Date == date).ToList();

                hours = entries.Aggregate(0.0, (t, i) => t + i.Hours);

 

                timesheet.Write("<td>");

                if (hours > 0)

                {

                    timesheet.Write(hours);

                }

                else

                {

                    timesheet.Write("&nbsp;");

                }

                timesheet.Write("</td>");

            }

 

            timesheet.WriteLine("</tr>");

        }

        timesheet.WriteLine("</tbody></table></body></html>");

    }

}

 

Скачать все вместе можно здесь.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.