Untitled

Привет! Меня зовут Настя Николаева, лид цифровой трансформации в компании Bimeister. И я хочу рассказать, как мы собирали единый роадмап компании с помощью плагина Structure Jira.

Наверное, в каждой компании, где есть несколько команд разработки, существуют подобные проблемы:

  1. у каждой команды свои правила и инструменты для построения роадмап

  2. некоторые роадмапы не найти, они не находятся в свободном доступе

  3. продуктовый роадмап и проектные живут в разных местах и не синхронизированы

  4. нет единого общего роадмап, по которому можно увидеть статус продуктовой разработки и проектных обязательств

Решив эти проблемы, мы сможем достигнуть поставленных целей:

  1. Прозрачные и понятные сроки исполнения проектных и продуктовых обязательств

  2. Выявление рисков на ранних этапах исполнения работ

Итак, верхнеуровнево мы наметили такие шаги:

Untitled

Шаг 1 - выбираем единый инструмент построения роадмап

Так как у нас вся продуктовая разработка ведется в Jira, то и собирать единый роадмап правильно было бы в jira, избежав копи-пасти и дублирования информации.

В процессе ресеча различных плагинов и опыта других компаний мы выбрали Structure в Jira.

Structure — плагин для Jira, с помощью которого можно гибко визуализировать и структурировать задачи в jira в виде иерархии.

Structure позволяет на основе задач jira:

  • выстроить список задач jira в любой иерархии, по любому нужному запросу либо же вручную по одной задаче

  • сгруппировать списки по любому атрибуту тикета, отфильтровать ненужные, отсортировать так, как требуется

  • отобразить и показать требуемые поля тикетов

  • с помощью формул и языка запросов можно добавить нужные аналитические параметры и отобразить любые данные по тикетам

  • добавить gantt chart и ресурсы

  • и многое-многое другое

Шаг 2 - собираем роадмап

Первая проблема, с которой столкнулись - просто так, “сходу”, роадмап нам было не собрать, так как все команды ведут тикеты в jira по своим правилам; признаков или каких-то других атрибутов, по которым мы можем одним запросом вытащить все задачи, не было. Следовательно, его нужно было добавить. Для этого сделали несколько доработок по таскам:

  • унифицировали тип задач feature и story, внедрили единый флоу по ним. Это нам поможет собрать единую структуру роадмап по всем командам разработки

  • добавили обязательные поля, по которым собираем structure

    • Teamname

    • Проект

    • Продукт

    • Даты старта и окончания проектных работ

Это поможет правильно сгруппировать задачи, выстроить модель рисков и сделать связь продуктового роадмап и календарно-сетевого графика проекта.

Untitled

Дальше создаем структуру, задаем название. Structure создаем пустой, без преднастроек, которые предлагает сам плагин. Всю кастомизацию делаем руками.

Untitled

Чтобы структуру наполнить можно воспользоваться 2-мя способами:

  • добавить задачи вручную - это довольно долго, подходит для небольших роадмапов и небольших команд

  • добавить задачи автоматически по какому-то условию. Этим способом мы и наполняем нашу structure.

Нас интересуют задачи с типом таски = feature, так как это для нас ключевая сущность.

Untitled

Дальше, для наглядности, фичи нужно сгруппировать. Группировать можно по любому полю тикета jira.

Мы сделаем по Эпику, так как Эпик - это сущность большого раздела для разработки продукта. Получаем список всех эпиков с вложенными в них фичами.

Чтобы разложить эпики по продукту - добавляем группировку по продукту. А чтобы разложить продукты по проектам - группируем по проектам.

Untitled

Итог: мы можем видеть все проекты компании, в каждом проекте видим продукты, которые подключены, и в каждом продукте видим эпики и фичи, которые разрабатываются для проекта.

Можно через связи добавить к фичам более мелкую детализацию - стори и таски на аналитику и дизайн - для этого так же через автоматизацию добавляем задачи через связь contents или содержит.

Шаг 3 - соединяем продуктовый роадмап и календарно-сетевой график проектов

Базовый вид structure для нас не самый информативный и не содержит нужные нам для анализа данные. Поэтому мы разработали свой вид structure.

Для этого добавили больше столбцов:

  • приоритет задач

  • статус задач

  • название команды, которая делает разработку

  • lead time по задачам, которые уже выполнены

  • фактические даты старта и окончания работ - даты продуктовой команды, в которые задачи будут браться в работу

  • плановые даты старта и окончания работ - проектные даты, которые были согласованы с руководителями проектов и заказчиком, и по которым и строится календарно-сетевые графики проекта

  • ФТТ - какие требования заказчика по договору решаются в рамках каждого тикета

  • прогресс по задаче - процент выполнения задачи в зависимости от ее статуса

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

Untitled

А теперь добавим немного наглядности. Мы разработали модель рисков, по которой можно отследить, есть ли у тикета риск быть не выполненным в срок в зависимости от дат, статусов и типа задачи.

За основу мы взяли стандартный параметр 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() &gt; 0):
(
if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
else if progress = 0 :renderStatus(&quot;652CB3&quot;, &quot;icn-01&quot;, &quot;Работы запланированы&quot;, 7)
else : renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Опережаем сроки!&quot;, 7)
)
 
/*Если дата старта сегодня и работы начались*/
else if StartDate=TODAY():
(
if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
else if progress&gt;0.1: renderStatus(&quot;59B161&quot;, &quot;icn-03&quot;, &quot;Опережаем сроки!&quot;, 7)
else : renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
)
 
/*Если дата старта прошла*/
else if StartDate &lt;= TODAY() :
(
    if EndDate - TODAY() &gt; 60: /*до конца более 60 дней*/
    (
    if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8 : renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Опережаем сроки!&quot;, 7)
    else if progress &gt;= 0.4 : renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else: renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
    )
    else if EndDate - TODAY() &gt; 30: /*до конца более 30 дней*/
    (
    if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8: renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else if progress &gt;= 0.4: renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    else: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    )
    else if EndDate - TODAY() &gt; 0: /*до конца более 0 дней*/
    (
    if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8: renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else if progress &gt;= 0.4: renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    else: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    )
    /*deadline прошел*/
    else if EndDate &lt;= TODAY():
    (
    if progress =1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    else if progress &gt;= 0.4: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    else: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    )
 
)

/*Если прогресс=1*/
else if progress =1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
 
/*Если даты не заполнены*/
else   : renderStatus(&quot;000000&quot;, &quot;icn-03&quot;, &quot;Незаполнены даты&quot;, 7)

)
/-------------------------------------------------------------------/
/2 - РИСКИ ДЛЯ СТОРЕЙ И ТАСОК/
else if (type="Story" or type="Task"):
(
/*Если статус Бэклог и даты старта еще далеко впереди*/
if (StartDate - TODAY() &gt; 0):
(
if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
else if progress = 0 :renderStatus(&quot;652CB3&quot;, &quot;icn-01&quot;, &quot;Работы запланированы&quot;, 7)
else : renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Опережаем сроки!&quot;, 7)
)
 
/*Если дата старта сегодня и работы начались*/
else if StartDate=TODAY():
(
if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
else if progress&gt;0.1: renderStatus(&quot;59B161&quot;, &quot;icn-03&quot;, &quot;Опережаем сроки!&quot;, 7)
else : renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
)
 
/*Если дата старта прошла*/
else if StartDate &lt;= TODAY() :
(
    if EndDate - TODAY() &gt; 7: /*до конца более 7 дней*/
    (
    if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8 : renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Опережаем сроки!&quot;, 7)
    else if progress &gt;= 0.4 : renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else: renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
    )
    else if EndDate - TODAY() &gt; 4: /*до конца более 4 дней*/
    (
    if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8: renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else if progress &gt;= 0.4: renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    else: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    )
    else if EndDate - TODAY() &gt; 0: /*до конца более 0 дней*/
    (
    if progress = 1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8: renderStatus(&quot;FFAF00&quot;, &quot;icn-02&quot;, &quot;В рамках сроков&quot;, 7)
    else if progress &gt;= 0.4: renderStatus(&quot;B610D2&quot;, &quot;icn-03&quot;, &quot;Нужно поторопиться&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    else: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Есть риск не успеть&quot;, 7)
    )
    /*deadline прошел*/
    else if EndDate &lt;= TODAY():
    (
    if progress =1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
    else if progress &gt;= 0.8: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    else if progress &gt;= 0.4: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    else if progress &gt; 0: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    else: renderStatus(&quot;EF4B59&quot;, &quot;icn-03&quot;, &quot;Deadline наступил!&quot;, 7)
    )
 
)

	else if progress =1: renderStatus(&quot;59B161&quot;, &quot;icn-01&quot;, &quot;Работы завершены!&quot;, 7)
 
	/*Если даты не заполнены*/
	else   : renderStatus(&quot;000000&quot;, &quot;icn-03&quot;, &quot;Незаполнены даты&quot;, 7)

)

Модель рисков в виде таблицы:

Untitled

Теперь система нам подсветит те задачи, по которым есть риск быть не выполненными в срок, и даст время команде нивелировать его.

Ранее уже писала, что важная составляющая structure - это возможность положить список задач на ганта.

Для добавления Ганта нужно выбрать дополнительный слой структуры Gantt Chart и в параметрах Ганта определить даты, по которым будет строиться гант.

Untitled

Гант также довольно гибкий:

  • на него можно добавить важные Milestone

  • добавить маркеры, по которым отслеживаются важные проектные даты или события

  • добавить даты релизов и тд

Untitled

Итого: мы получили единый роадмап с важными для нас параметрами, с автоматическим добавлением задач и автоматическим определением рисков. Роадмап доступен каждому в компании, все прозрачно и доступно в едином месте по одной ссылке. Роадмап актуален в любое время, так как вся информацию тянется из тикетов jira, и при обновлении тикетов роадмап обновляется автоматически.

Тем самым полечили боли и достигли поставленных целей.

Вывод

Structure один из самых гибких инструментов, с помощью которых мне приходилось строить единые большие роадмапы, и самый эффективный.

Дальше в планах роадмап развивать:

  1. добавить полный цикл проекта - задачи внедрения, подготовки договоров и прочее;

  2. доработать модель рисков - добавить зависимость от этапов проекта, разработки, типов задач.

Комментарии (2)


  1. cema93
    14.06.2023 21:00

    Спасибо за написанный материал. Увы, разработчик jira ушел с Российского рынка...

    Пора писать такую статью про российские аналоги jira как один из поводов импортозамещения


    1. Anastasiya_Nikolaeva Автор
      14.06.2023 21:00

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