Я недавно писал о том, как пользоваться отчетом Burndown and Burn Rate в TFS.
Сейчас хочу рассказать немного про внутреннюю кухню. Как же данные из issue tracker, например значения полей remaining work и completed work у элементов task, превращаются красивую картинку отчета?
На самом деле, никакой магии здесь нет. Всё предельно просто.
Данные issue tracker хранятся в обычной БД SQL Server с именем TFS_имяколлекциипроектов. Обычная такая транзакционная БД. С немного страшной структурой, ну с кем не бывает, и в MS страшные вещи проектируют.
Например work items скорее всего лежат в какой-то из этих таблиц (а может и во всех сразу): WorkItemsAre, WorkItemsWere, WorkItemsLatest, WorkItemsDestroyed, с любопытными полями вроде “Not A Field” и “Fld10013”.
Нет, данные из этих таблиц не идут напрямую на картинку отчета.
Раз в 2 минуты (по умолчанию) TFS перекидывает данные в data warehouse (склад данных). БД TFS_Warehouse. Обычный такой data warehouse, с таблицами фактов, таблицами измерений. Зачем это делается? Профиль нагрузки на транзакционную БД и на data warehouse очень разный. Транзакционная БД должна хранить только текущее состояние данных, и обеспечивать обработку большого количества запросов чтения/записи. Data warehouse должен хранить огромные объемы (в идеале – всю историю изменений рабочих элементов TFS), при этом данные там не изменяются и не удаляются, только добавляются. Структура базы данных оптимизирована для использования в отчетах и аналитике.
Далее раз в 2 часа (по умолчанию) данные попадают в OLAP-кубы SQL Server Analysis Services (база данных Tfs_Analysis в SSAS). При этом там производятся всякие расчеты хитрые, например предварительный расчет различных агрегированных показателей, для того чтобы потом быстро отвечать на аналитические запросы.
И только после этого, SQL Server Reporting Services на основе данных из SQL Server Analysis Services строит отчеты. Они строятся тоже не в real-time по запросу пользователя, а периодически обновляются в фоновом режиме. Как часто – уже не помню, но чаще чем 1 раз в час.
Время последнего обновления данных в SSAS обычно указывается в отчете снизу справа с подписью “Data updated”. Время последнего обновления отчета в SQL Server Reporting Services – там же, с подписью “Generated”.
В разработке собственных отчетов (или правке стандартных) тоже нет никакой магии. Это обычные отчеты SQL Server Reporting Services.
Если зайти в веб-интерфейс SQL Server Reporting Services, найти там список отчетов, у каждого отчета в контекстном меню есть пункт “Edit in report builder”.
Он открывает report builder – GUI-приложение, которое инсталлируется через Click once, и позволяет полноценно редактировать отчет.
Тут мы можем настраивать источники данных, привязывать их к элементам отчета, стили всякие настраивать.
Посмотрим на то, откуда берутся данные для отчета Burn Rate. Он строится на основе набора данных dsVelocity, который берет данные из SSAS с помощью вот такого MDX-запроса:
WITH MEMBER [Measures].[Remaining Work] AS [Measures].[FactCurrentWorkItem Microsoft_VSTS_Scheduling_RemainingWork] MEMBER [Measures].[Completed Work] AS [Measures].[FactCurrentWorkItem Microsoft_VSTS_Scheduling_CompletedWork] SELECT { [Measures].[Work Item Count], [Measures].[Remaining Work], [Measures].[Completed Work] } ON COLUMNS, { NonEmpty( [Work Item].[System_State].[System_State], [Measures].[Work Item Count] ) } ON ROWS FROM ( SELECT CrossJoin( StrToMember("[Team Project].[Project Node GUID].&[{" + @ProjectGuid + "}]"), StrToSet(@StateParam), StrToSet(@AreaParam), StrToSet(@IterationParam), Except( Descendants(StrToSet(@WorkItemTypeParam)), [Work Item].[System_WorkItemType].[All] + StrToSet(@WorkItemsToExclude) ) ) ON COLUMNS FROM [Team System] )
Просто взяли данные по рабочим элементам, отфильтровали по параметрам, из мер (measure) нас интересуют количество рабочих элементов, оставшаяся и выполненная работа. Я не до конца этот запрос понимаю, но при желании разобраться можно.
Дальше для вычисления собственно current velocity и required velocity используются вот такие нехитрые выражения:
CurrentVelocity =IIF( Parameters!YAxis.Value = "hours", Fields!Completed_Work.Value, IIF( Fields!System_State.Value=Parameters!ClosedName.Value, Fields!Cumulative_Count.Value, 0 ) ) / Code.DaysCompleted(Parameters!StartDateParam.Value, Parameters!EndDateParam.Value, Parameters!NonWorkDays.Value) RequiredVelocity =IIF( Parameters!YAxis.Value = "hours", Fields!Remaining_Work.Value, IIF( Fields!System_State.Value<>Parameters!ClosedName.Value, Fields!Cumulative_Count.Value, 0 ) ) / Code.DaysRemaining(Parameters!StartDateParam.Value, Parameters!EndDateParam.Value, Parameters!NonWorkDays.Value)
Если отбросить кучерявые конструкции IIF, видно, что здесь работа делится на рабочие дни. Нерабочие дни при этом вычисляются с использованием параметра “NonWorkDays”, значение по умолчанию у которого равно “0,6” – подозреваю, что это индексы воскресенья и субботы соответственно.
Домашнее задание: отредактируйте график Burndown так, чтобы в нём не было выходных дней. И еще придумайте, как нам быть с тем, что сейчас в России заканчиваются 3-дневные выходные, а значит, график Burn Rate будет врать.
–
Павел Сурменок