13 февраля 2017
Блог
Как мы заполняем 100 таймшитов за 2 минуты
Проблема — бардак в заполнении таймшитов
Для 99% компаний-разработчиков учет рабочего времени программистов нужен как воздух, чтобы считать затраты. Поэтому многие заставляют сотрудников заполнять таймшиты (timesheet, своего рода — табель учета рабочего времени). Часто это подразумевает заполнение вручную таблицы с задачами и потраченным на них временем.
99% программистов, мягко говоря, недолюбливают эту процедуру. Еще бы, отличная перспектива для пятничного вечера — минут 30 сидеть и тупо копипастить все свои задачи за неделю из трекинговой программы в таймшит. А, как известно, когда люди делают тупую и ненавистную работу, то результат бывает так себе. Вот и в нашей компании половина таймшитов в пятничный вечер были далеки от совершенства. В чем это выражалось?
В первую очередь в том, что таймшиты не отражали всех «боевых» задач, которые решали разработчики. Программист мог написать, что просто все 40 часов работал по такому-то проекту. Или мог задачи из одного проекта по ошибке отнести в таймшите к другому.
Для борьбы со всем этим беспорядком прекрасная сотрудница из проектного офиса (фотография которой, несомненно, украсила бы статью, но публиковать ее мы все-таки не будем) нежно, но неотступно прессинговала 100 человек половину рабочего дня в понедельник. Потом по длинному чек-листу проверяла 1200 табличных строк всех таймшитов. И только ко вторнику руководство имело полную картину.
Результат: с задержкой в 3 рабочих дня руководители, наконец, получали отчет по затраченному рабочему времени за прошлую неделю.
Как было. Процедура сбора таймшитов.
Более трех дней
Решение — автоматизация процесса заполнения таймшитов
Решение проблемы выкристаллизовалось постепенно. Мы рассуждали так. Если программист всю свою работу фиксирует в трекере, то почему бы не брать данные о задачах и количестве рабочих часов прямо оттуда? Это позволит быстро получать информацию для анализа трудозатрат, кадрового документооборота и других менеджерских задач. Дальше дело было за малым — найти красивое технологическое решение по интеграции наших трекеров с системой, в которой мы учитываем рабочее время (Microsoft Project Server — подробнее о нем здесь)
Как стало. Процедура заполнения таймшитов
До 100 штук за 2 минуты! Максимальное время сбора данных — 2 часа 10 минут.
Что мы получили?
В MS Project Server появилась волшебная кнопочка, которая позволяет сделать таймшит за 2 минуты. Заходишь в пятницу в Project Server, он автоматически строит таймшит по данным из трекера за неделю. Туда остается добавить только данные об отгулах, отпуске, больничном, если были. И вуаля — нажимаешь кнопку, подтверждаешь, что все правильно, и таймшит готов.
Надо сказать, что волшебство работает только у тех, кто всю неделю соблюдал технологию ведения проектов в трекере: фиксировал задачи, а также планируемое и фактическое время на их выполнение. Эти хорошие ребята в пятницу получают плюсик в карму и автоматическое заполнение таймшита. Автоматически заполненный таймшит:
Бонусы волшебной кнопки
1. Сэкономили 200 рабочих часов
Если раньше у сотрудника на заполнение таймшитов уходило 30-40 минут, то сейчас 2 минуты. А если пересчитать на 100 человек в месяц, то мы сэкономили около 200 рабочих часов.
Сюда можно добавить еще 32 рабочих часа, которые уходили на проверку правильности заполнения таймшитов, вместо которых теперь уходит только 4 часа в месяц. Экономия времени объясняется просто: теперь в таймшиты выгружаются реальные задачи из трекеров, а не те, которые люди вбили руками.
2. Оперативно получаем аналитику по проектам за неделю
Теперь в 19:00 в пятницу полностью заполнены 91% таймшитов, и у менеджеров есть аналитика трудозатрат по проектам уже в конце недели. Раньше к этому времени было собрано меньше половины.
3. Привели к единообразию стандарты ведения всех проектов
Теперь во всех трекинговых системах все проекты, клиенты и прочие данные обозначены единообразно. Везде проставлены цифры по запланированному и фактическому времени на выполнение задач. А это уменьшает наши риски и повышает управляемость проектов.
4. Упростили контроль того, как соблюдается технология ведения проектов
Раньше, чтобы понять, как ведется проект, нужно было вручную заглянуть в трекер. Теперь контроль происходит автоматически при отправке таймшита. Например, если в Redmine не проставлены статусы задач и время на их выполнение, то эти задачи просто не выгрузятся в таймшит. И наоборот, если проектный офис видит правильно заполненный таймшит — это подтверждает, что его автор заполнил все поля в трекинговой программе.
По факту на выходе мы получили реинжиринг всего бизнес-процесса. Оборудовали работу так, как удобно нам, с нашими комплексными большими проектами.
5. Получаем аналитику по любым параметрам уже в пятницу
Если раньше аналитика за прошлую неделю приходила к менеджерам только во вторник-среду, то теперь уже в конце текущей недели, вечером в пятницу, 90% информации по таймшитам собрано. На основе ее выстроен OLAP-куб.
Сколько багов за неделю было исправлено? Сколько часов ушло на каждый из проектов? Где возникли узкие места и почему? Анализировать данные теперь можно более оперативно. Но это не предел. В следующий раз мы расскажем о том, как можно получать сводные данные по всем проектам из трекеров в режиме реального времени при помощи усовершенствованной версии нашего OLAP-куба.
А сейчас подробности, мясо и куски кода.
Автоматизация бизнес-процесса по шагам
Шаг 1. Стандартизировали ведение проектов в разных трекинговых системах
Сложность задачи заключалась в том, что в компании для управления разными по технологической начинке проектами используются три разных трекера (системы для управления проектами). Это продиктовано требованиями с точки зрения технологий и спецификой самих проектов.
Стандарты ведения проектов в разных трекерах немного отличались. К примеру, для разных проектов могли быть приняты разные воркфлоу. Или комплексный проект для одного и того же заказчика мог в отделе мобильной разработки (в Redmine) называться, условно, «Мобилка для крутого клиента N», а в отделе веб-разработки (в TFS) «Внутренний сайт компании N».
Для интеграции весь этот зоопарк пришлось причесать — обеспечить, чтобы в каждой системе одинаковым проектам давалось идентичное название, везде было указано время выполнения задач и применялась общая методология ведения.
На уровне технологий это потребовало прикрутки некоторых возможностей к самим трекерам. Например, в TFS нет колонки, в которую можно вписать количество часов, которое ты потратил на задачу в определенную дату.
Шаг 2. Написали страницу, которая импортирует записи в MS Project Server
Расскажем, как можно сделать такую страницу. Наметим краткий план того, что нужно:
- Добавить кнопку в MS Project.
- Авторизоваться в трекинговой системе и считать данные по трудозатратам оттуда.
- Отобразить полученные данные пользователю для проверки.
- Записать данные в MS Project.
2.1. Кнопочка
Давайте рассмотрим, как можно расширить интерфейс MS Project Server. Поскольку, как всем известно, MS Project Server работает поверх MS SharePoint Server, можно воспользоваться возможностями последнего, чтобы добавлять различные элементы в интерфейс MS Project. В частности, нам нужно было, чтобы пользователь запускал импорт непосредственно из страницы с таймшитом. Для этого в Ribbon таймшита можно добавить специальную кнопочку и связать ее с соответствующим действием. Для того чтобы иметь возможность развернуть это в MS SharePoint необходимо создать Feature, который содержит элемент типа Custom Action.
<CustomAction Id="EBT.MSP.TimesheetSync.ImportFromRedmine.CustomAction"
Location="CommandUI.Ribbon" Title="Import data from RedMine" > …</CustomAction>Внутри этого CA определяем соответствующее расширение.
<CommandUIDefinition Location="Ribbon.ContextualTabs.Timesheet.Home.Sheet.Controls._children"> … </CommandUIDefinition>
Собственно, это довольно хорошо известно. Единственная проблема, которая может возникнуть — как вычислить упомянутый Location. Мы хотим, чтобы кнопка появилась здесь.
Информации в интернете по этому вопросу не так много, поэтому основным источником информации и является файл pwaribbon. Файл довольно большой, но разобраться в нем можно. После того как мы нашли нужный Location, все просто:
Добавляем в него кнопку.
<Button Id="EBT.MSP.TimesheetSync.Import.ANCHOR.RedmineButton"
LabelText="Import from Redmine" Image16by16="/_layouts/15/EBT.MSP.TimesheetSync/img/rm_small.png" Command="EBT.MSP.TimesheetSync.Import.ANCHOR.RedmineButton.Command" TemplateAlias="o1" />И определяем соответствующую команду.
<CommandUIHandler
Command="EBT.MSP.TimesheetSync.Import.ANCHOR.JiraButton.Command" CommandAction="javascript:function ProjectCenterExtension() { var _grid; // Display surface (a view) of the JS Grid. var _satellite; // Control wrapper for the JS Grid. var props = window.timesheetComponent.get_TimesheetSatellite().get_impl()._pageProperties; window.location = '_layouts/15/EBT.MSP.TimesheetSync/ImportTimesheetData.aspx?ts=' + props.tsUid + '&prd=' + props.prdUid + '&datasource=jira_ebt'; }; ProjectCenterExtension();" />Как видно из приведенного кода, команда просто перенаправляет пользователя на соответствующую страницу типа ApplicationPage. Эта страница, которую мы написали сами. Собственно внутри нее и происходят все дальнейшие «грязные» дела.
А именно:
- Авторизация в трекинговой системе и считывание данных.
- Отображение этих данных на странице, для того чтобы пользователь мог увидеть, что добавится в его Timesheet.
- Запись информации в MS Project.
2.2. Чтение данных
Тут возникло две проблемы:
- Как авторизоваться в трекерах для получения данных.
- Как сопоставить пользователя MS Project с пользователем трекинговой системы.
Глобально тут можно выделить 2 подхода. Забегая вперед, скажем, что нам пришлось применить оба подхода для разных трекеров:
- Завести некого суперпользователя, который может получить все данные из трекера. А далее пытаться сопоставлять имена пользователя MS Project Server и трекеров.
- «Пробросить» авторизацию из MS Project Server в трекер. Здесь есть очевидный плюс — сопоставление не нужно, можно просто получать записи пользователя по его «проброшенной» учетной записи. Но с этим связаны и некоторые проблемы, о которых чуть позже.
Решение для Redmine и TFS
В нашем конкретном случае эти трекеры «живут» в нашей инфраструктуре, поэтому мы решили пойти первым путем.
В случае Redmine все просто — в Redmine есть функция Log Work, которая работает ровно так, как нужно. То есть человек может зайти в задачу и отметить, сколько часов он потратил на задачу такого-то числа.
Дальше дело техники — сделать представление в базе данных Redmine (у нас Redmine использует MySQL), и все готово. Задача решена, мы можем получить данные.
В случае с системой TFS все чуть сложнее. Такой возможности, как в Redmine, в ней нет. Есть определенные Add-In’ы, которые ее дают, например, www.tfs-timetracker.com — вещь интересная, хотя и недешевая.
Но можно к этой задаче подойти с организационной точки зрения. Как? В TFS есть поле Completed Work, в которое разработчики должны записывать суммарно потраченные часы на задачу. В шаблонах проектов типа Scrum такое поле скрыто, но его можно отобразить. Далее по истории изменений Work Item можно отследить изменение этого поля и понять, когда добавлялись часы, когда, кем и сколько времени было потрачено. Конечно это требует очень плотной работы с трекером со стороны разработчика. Он не может зарепортить время задним числом, но, тем не менее, если все делать вовремя и аккуратно, то такой подход очень неплохо работает. Более того, он заставляет разработчиков вести себя более дисциплинированно.
JIRA
В нашем случае JIRA «хостится» в стороннем окружении. Поэтому ни суперпользователя, ни доступа к ее БД у нас нет. Но зато в JIRA есть замечательный REST API, который предоставляет то, что нужно.
Чтобы считать данные, нужно авторизоваться по протоколу OAuth. После запуска импорта из JIRA человек перенаправляется на страницу авторизации JIRA, авторизуется там (если пользователь еще не авторизован), после чего возвращается на страницу импорта.
(К сожалению JIRA поддерживает только OAUTH 1, который на текущий момент немного староват, но прорваться можно.) Получив OAUTH token мы:
- Вытаскиваем имя автора методом.
api/v2/myself
И таким образом никакого сопоставления имен не надо, мы просто берем пользователя, который залогинился в JIRA. - Далее ищем все задачи, для которых пользователь залогировал время в определенный период.
api/v2/myself/search?jql=worklogDate>={start_date} and worklogDate<{end_date} and timespent>0 and worklogAuthor={author}&fields=summary,project,parent,timeoriginalestimate,{pswa}&startAt=0
На первый взгляд, это все, что нужно, но не совсем. MS Project поддерживает работу в режиме делегата, когда человек что-то делает от имени другого, и наши менеджеры активно этим пользуются. Здесь, конечно, случаются определенные коллизии, поскольку, если человек A олицетворяет в MS Project человека Б (обычно руководитель — подчиненного, либо ответственное лицо — сотрудника), то в JIRA он его не олицетворяет, а остается самим собой. И подобным запросом он получит задачи, назначенные на него самого, то есть на человека А, которые запишутся в timesheet человеку Б. Более того, к сожалению, нет метода (или мы не нашли его), чтобы получить все задачи по всем проектам, в которых указанный пользователь залогировал свои трудозатраты. И это еще не все — человек А вовсе может не иметь в JIRA соответствующих полномочий на чтение нужных задач. Впрочем, последний вопрос можно решить организационно.
Для того чтобы перебрать все задачи, приходится сначала получить все проекты методом:
api/v2/project
Далее пробежаться по всем проектам и достать все задачи.
api/v2/search?jql=project={projectKey}&fields=summary,project,parent,timeoriginalestimate,{pswa}&startAt=0
Для каждой найденной задачи получить залогированое время и выбрать то, что попадает в нужный период и принадлежат нужному автору.
api/v2/issue/{id}/worklog
В этом случае проблема сопоставления имен опять, конечно, весело встречает нас. Кроме того, такой процесс занимает очень много времени. Намного лучше, наверное, тут и не придумать, впрочем, если есть лучшее решение, были бы рады услышать.
2.3. Отображение данных
Тут особо рассказать нечего — код на старом добром ASP.Net, который отрисовывает полученные данные.
2.4. Запись данных в Timesheet
Итак, мы перешли на специальную страницу, авторизовались, получили нужные записи, осталось записать их в MS Project. На самом деле у MS Project есть несколько видов API, но, поскольку мы работаем внутри него, мы использовали тот же самый, что используют страницы самого Project’a. API этот выглядит, скажем так, непривычно.
Например, чтобы считать ресурс, нужно вызвать метод.
var dataset = resourceSvc.ReadResource(userId);
И это вполне понятно, но потом придется провести еще много времени, пытаясь понять, где в этом DataSet нужная табличка, строка и колонка. Документации по этому вопросу довольно мало и, честно говоря, ни одного работающего с этим API примера добавления Timesheet Lines в Timesheet мы в интернете не нашли. Но, поковырявшись в «потрохах» MS Project Server с помощью ILSPY, мы в конце концов написали следующий код:
private void ReportTime(Guid timeSheetId, List<TaskData> tasks, Guid projectId)
{ var timesheetDs = _timeSheetSvc.ReadTimesheet(timeSheetId); TimesheetDataSet.LinesRow[] rows = (TimesheetDataSet.LinesRow[])timesheetDs.Lines.Select(); bool needUpdate = false; foreach (var task in tasks) { if (!task.Estimation.HasValue && !task.IgnoreEstimation) continue; Guid taskId = new Guid(task.Id); Guid assnId = new Guid(task.AssnId); bool isRowExist = false; foreach (var row in rows) { if (row.ASSN_UID != assnId) continue; isRowExist = true; AddActual(timesheetDs, task, row); } if (!isRowExist) { TimesheetDataSet.LinesRow line = timesheetDs.Lines.NewLinesRow(); line.TS_UID = timeSheetId; line.TASK_UID = taskId; line.PROJ_UID = projectId; line.ASSN_UID = assnId; line.TS_LINE_CACHED_ASSIGN_NAME = task.Name; line.TS_LINE_UID = Guid.NewGuid(); line.TS_LINE_CLASS_UID = PSLibrary.TimesheetConst.const_StandardLineClassGuid; line.TS_LINE_STATUS = (byte)LineStatus.Pending; line.TS_LINE_VALIDATION_TYPE = (byte)ValidationType.Verified; timesheetDs.Lines.AddLinesRow(line); _timeSheetSvc.PrepareTimesheetLine(timeSheetId, ref timesheetDs, new Guid[] { line.TS_LINE_UID }); AddActual(timesheetDs, task, line); } needUpdate = true; } if (needUpdate) { Guid jobUid = Guid.NewGuid(); _timeSheetSvc.QueueUpdateTimesheet(jobUid, timeSheetId, timesheetDs.GetChanges() as TimesheetDataSet); TaskUtils.WaitForQueue(_queueSvc, jobUid); } }Этот код, собственно, и записывает нужные данные в MS SharePoint. Честно говоря, проблем при работе с MS Project было больше всего. Про это можно написать целую отдельную статью.
Шаг 3. Откатали технологию на тестовой группе пользователей
Мы внедряли «волшебную кнопку» отдельно для каждого трекера, каждый раз охватывая по ~30% сотрудников, и каждый раз перед полноценным запуском тренировались на небольшой группе из 3-5 «подопытных» пользователей, с которыми в спокойной обстановке проходили весь процесс и доводили его до production ready состояния, чтобы не получить в пятницу вечером шквал вопросов и негодования сразу от 30 человек.
Шаг 4. Собрали обратную связь
Что говорят сотрудники True Engineering о «волшебной кнопке» для заполнения таймшитов
Юрий Булкин, главный Java-архитектор
Огонь!!! Когда я первый раз нажал кнопку и тут же увидел полностью заполненный таймшит — я просто глазам не поверил. Мне много раз говорили, что все будет так происходить. И я понимал, как это должно выглядеть. Но когда я сам получил автоматически заполненный двадцатью задачами таймшит, это было так круто, что я только тогда осознал все удобство этой штуки.
Александра Очеретинская, руководитель проектного офиса
Мы поняли: то, что мы в пятницу заставляем программистов заполнять таймшиты — это неполезная, неинтеллектуальная деятельность. Мы требуем вручную вносить информацию, которая уже существует, просто в другом месте и в другом виде. И мы можем избавить людей от этой ненужной работы. Если люди в любом случае уже работают по задачам, которые заведены в трекерах, то почему бы просто не выгружать эту информацию для учета? Так мы и сделали.