Привет! Меня зовут Настя Николаева, лид цифровой трансформации в компании Bimeister. И я хочу рассказать, как мы собирали единый роадмап компании с помощью плагина Structure Jira.
Наверное, в каждой компании, где есть несколько команд разработки, существуют подобные проблемы:
у каждой команды свои правила и инструменты для построения роадмап
некоторые роадмапы не найти, они не находятся в свободном доступе
продуктовый роадмап и проектные живут в разных местах и не синхронизированы
нет единого общего роадмап, по которому можно увидеть статус продуктовой разработки и проектных обязательств
Решив эти проблемы, мы сможем достигнуть поставленных целей:
Прозрачные и понятные сроки исполнения проектных и продуктовых обязательств
Выявление рисков на ранних этапах исполнения работ
Итак, верхнеуровнево мы наметили такие шаги:
Шаг 1 - выбираем единый инструмент построения роадмап
Так как у нас вся продуктовая разработка ведется в Jira, то и собирать единый роадмап правильно было бы в jira, избежав копи-пасти и дублирования информации.
В процессе ресеча различных плагинов и опыта других компаний мы выбрали Structure в Jira.
Structure — плагин для Jira, с помощью которого можно гибко визуализировать и структурировать задачи в jira в виде иерархии.
Structure позволяет на основе задач jira:
выстроить список задач jira в любой иерархии, по любому нужному запросу либо же вручную по одной задаче
сгруппировать списки по любому атрибуту тикета, отфильтровать ненужные, отсортировать так, как требуется
отобразить и показать требуемые поля тикетов
с помощью формул и языка запросов можно добавить нужные аналитические параметры и отобразить любые данные по тикетам
добавить gantt chart и ресурсы
и многое-многое другое
Шаг 2 - собираем роадмап
Первая проблема, с которой столкнулись - просто так, “сходу”, роадмап нам было не собрать, так как все команды ведут тикеты в jira по своим правилам; признаков или каких-то других атрибутов, по которым мы можем одним запросом вытащить все задачи, не было. Следовательно, его нужно было добавить. Для этого сделали несколько доработок по таскам:
унифицировали тип задач feature и story, внедрили единый флоу по ним. Это нам поможет собрать единую структуру роадмап по всем командам разработки
добавили обязательные поля, по которым собираем structure
Teamname
Проект
Продукт
Даты старта и окончания проектных работ
Это поможет правильно сгруппировать задачи, выстроить модель рисков и сделать связь продуктового роадмап и календарно-сетевого графика проекта.
Дальше создаем структуру, задаем название. Structure создаем пустой, без преднастроек, которые предлагает сам плагин. Всю кастомизацию делаем руками.
Чтобы структуру наполнить можно воспользоваться 2-мя способами:
добавить задачи вручную - это довольно долго, подходит для небольших роадмапов и небольших команд
добавить задачи автоматически по какому-то условию. Этим способом мы и наполняем нашу structure.
Нас интересуют задачи с типом таски = feature, так как это для нас ключевая сущность.
Дальше, для наглядности, фичи нужно сгруппировать. Группировать можно по любому полю тикета jira.
Мы сделаем по Эпику, так как Эпик - это сущность большого раздела для разработки продукта. Получаем список всех эпиков с вложенными в них фичами.
Чтобы разложить эпики по продукту - добавляем группировку по продукту. А чтобы разложить продукты по проектам - группируем по проектам.
Итог: мы можем видеть все проекты компании, в каждом проекте видим продукты, которые подключены, и в каждом продукте видим эпики и фичи, которые разрабатываются для проекта.
Можно через связи добавить к фичам более мелкую детализацию - стори и таски на аналитику и дизайн - для этого так же через автоматизацию добавляем задачи через связь contents или содержит.
Шаг 3 - соединяем продуктовый роадмап и календарно-сетевой график проектов
Базовый вид structure для нас не самый информативный и не содержит нужные нам для анализа данные. Поэтому мы разработали свой вид structure.
Для этого добавили больше столбцов:
приоритет задач
статус задач
название команды, которая делает разработку
lead time по задачам, которые уже выполнены
фактические даты старта и окончания работ - даты продуктовой команды, в которые задачи будут браться в работу
плановые даты старта и окончания работ - проектные даты, которые были согласованы с руководителями проектов и заказчиком, и по которым и строится календарно-сетевые графики проекта
ФТТ - какие требования заказчика по договору решаются в рамках каждого тикета
прогресс по задаче - процент выполнения задачи в зависимости от ее статуса
В итоге что мы получаем - структурированный список задач продуктовой разработки в виде таблицы с прозрачными сроками, статусами по каждой.
А теперь добавим немного наглядности. Мы разработали модель рисков, по которой можно отследить, есть ли у тикета риск быть не выполненным в срок в зависимости от дат, статусов и типа задачи.
За основу мы взяли стандартный параметр Item Health и изменили формулу, по которой определяется риск:
формула:
WITH renderStatus(bgColor, iconId, status, padding) =
"""{panel:borderStyle=solid|borderColor=white|bgColor=#$bgColor}!https://d1.almworks.com/.files/weath_$iconId.png|width=20,height=20!!https://d1.almworks.com/.files/Spacer.gif|width=$padding!{color:white}*$status*{color}!https://d1.almworks.com/.files/Spacer.gif|width=$padding!{panel}""" :
/*1 - РИСКИ ДЛЯ ФИЧИ*/
if (type="feature"):
(
/*Если статус Бэклог и даты старта еще далеко впереди*/
if (StartDate - TODAY() > 0):
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress = 0 :renderStatus("652CB3", "icn-01", "Работы запланированы", 7)
else : renderStatus("59B161", "icn-01", "Опережаем сроки!", 7)
)
/*Если дата старта сегодня и работы начались*/
else if StartDate=TODAY():
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress>0.1: renderStatus("59B161", "icn-03", "Опережаем сроки!", 7)
else : renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
)
/*Если дата старта прошла*/
else if StartDate <= TODAY() :
(
if EndDate - TODAY() > 60: /*до конца более 60 дней*/
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8 : renderStatus("59B161", "icn-01", "Опережаем сроки!", 7)
else if progress >= 0.4 : renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else if progress > 0: renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else: renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
)
else if EndDate - TODAY() > 30: /*до конца более 30 дней*/
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8: renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else if progress >= 0.4: renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
else if progress > 0: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
else: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
)
else if EndDate - TODAY() > 0: /*до конца более 0 дней*/
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8: renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else if progress >= 0.4: renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
else if progress > 0: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
else: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
)
/*deadline прошел*/
else if EndDate <= TODAY():
(
if progress =1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
else if progress >= 0.4: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
else if progress > 0: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
else: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
)
)
/*Если прогресс=1*/
else if progress =1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
/*Если даты не заполнены*/
else : renderStatus("000000", "icn-03", "Незаполнены даты", 7)
)
/*-------------------------------------------------------------------*/
/*2 - РИСКИ ДЛЯ СТОРЕЙ И ТАСОК*/
else if (type="Story" or type="Task"):
(
/*Если статус Бэклог и даты старта еще далеко впереди*/
if (StartDate - TODAY() > 0):
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress = 0 :renderStatus("652CB3", "icn-01", "Работы запланированы", 7)
else : renderStatus("59B161", "icn-01", "Опережаем сроки!", 7)
)
/*Если дата старта сегодня и работы начались*/
else if StartDate=TODAY():
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress>0.1: renderStatus("59B161", "icn-03", "Опережаем сроки!", 7)
else : renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
)
/*Если дата старта прошла*/
else if StartDate <= TODAY() :
(
if EndDate - TODAY() > 7: /*до конца более 7 дней*/
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8 : renderStatus("59B161", "icn-01", "Опережаем сроки!", 7)
else if progress >= 0.4 : renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else if progress > 0: renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else: renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
)
else if EndDate - TODAY() > 4: /*до конца более 4 дней*/
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8: renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else if progress >= 0.4: renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
else if progress > 0: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
else: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
)
else if EndDate - TODAY() > 0: /*до конца более 0 дней*/
(
if progress = 1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8: renderStatus("FFAF00", "icn-02", "В рамках сроков", 7)
else if progress >= 0.4: renderStatus("B610D2", "icn-03", "Нужно поторопиться", 7)
else if progress > 0: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
else: renderStatus("EF4B59", "icn-03", "Есть риск не успеть", 7)
)
/*deadline прошел*/
else if EndDate <= TODAY():
(
if progress =1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
else if progress >= 0.8: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
else if progress >= 0.4: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
else if progress > 0: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
else: renderStatus("EF4B59", "icn-03", "Deadline наступил!", 7)
)
)
else if progress =1: renderStatus("59B161", "icn-01", "Работы завершены!", 7)
/*Если даты не заполнены*/
else : renderStatus("000000", "icn-03", "Незаполнены даты", 7)
)
Модель рисков в виде таблицы:
Теперь система нам подсветит те задачи, по которым есть риск быть не выполненными в срок, и даст время команде нивелировать его.
Ранее уже писала, что важная составляющая structure - это возможность положить список задач на ганта.
Для добавления Ганта нужно выбрать дополнительный слой структуры Gantt Chart и в параметрах Ганта определить даты, по которым будет строиться гант.
Гант также довольно гибкий:
на него можно добавить важные Milestone
добавить маркеры, по которым отслеживаются важные проектные даты или события
добавить даты релизов и тд
Итого: мы получили единый роадмап с важными для нас параметрами, с автоматическим добавлением задач и автоматическим определением рисков. Роадмап доступен каждому в компании, все прозрачно и доступно в едином месте по одной ссылке. Роадмап актуален в любое время, так как вся информацию тянется из тикетов jira, и при обновлении тикетов роадмап обновляется автоматически.
Тем самым полечили боли и достигли поставленных целей.
Вывод
Structure один из самых гибких инструментов, с помощью которых мне приходилось строить единые большие роадмапы, и самый эффективный.
Дальше в планах роадмап развивать:
добавить полный цикл проекта - задачи внедрения, подготовки договоров и прочее;
доработать модель рисков - добавить зависимость от этапов проекта, разработки, типов задач.