В данном посте я хочу затронуть тему получения отчетов о затраченном времени из 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; }
}
При старте приложения инициализируем наш класс:
public static void Main(string[] args)
{
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");
}
}
}
Добавим еще один класс, который будет содержать в себе данные о выполнении некоторой работы.
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> </th>");
foreach (var date in dates)
{
timesheet.Write("<th>" + date.ToShortDateString() + "</th>");
}
timesheet.WriteLine("</tr></thead><tbody>");
// Общеевремя.
timesheet.Write("<tr><td> </td><td> </td><td> </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(" ");
}
timesheet.Write("</td>");
}
timesheet.WriteLine("</tr>");
}
timesheet.WriteLine("</tbody></table></body></html>");
}
}
Скачать все вместе можно здесь.