Привет, Хабр! Меня зовут Алексей Панаэтов, я технический лидер Центра геосервисов в МТС Web Services. Каждый руководитель рано или поздно задается вопросом о количественной оценке эффективности команды. Метрики помогают принимать решения объективно, но их очень сложно строить, когда в работе активно используются множества разных инструментов: трекеры задач, системы контроля версии, корпоративный мессенджер и другие. К ним нужно сначала получить доступ, затем выгрузить и обработать нужные данные. Ad-hoc-методы вроде csv-файлов и таблиц Excel подходят для разового анализа, но их тяжело поддерживать.

Я тоже столкнулся с этой проблемой и для ее решения создал и выложил в открытый доступ код CaptainBridge — инструмент, который помогает собирать и строить метрики эффективности команды.

Что такое CaptainBridge

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

CaptainBridge делает две вещи:

  1. Собирает данные из инструментов команды (Jira, GitLab, Slack и прочих) в единое хранилище.

  2. Дает конструировать запросы к собранным данным на различных языках, начиная от Python и Mongo Aggregation Pipeline, заканчивая планируемым расширением функциональности до SQL и zero-code.

По сути, CaptainBridge делает за вас грязную работу. Не надо мучительно выгружать данные в CSV-файлы и импортировать в табличные редакторы типа Excel — можно сразу анализировать команду.

Но это далеко не все. Помимо возможности писать запросы к данным, Captain Bridge дает визуализировать результаты в виде простых графиков и таблиц. Разработчики также планируют интеграцию с популярной платформой интерактивной графики и дашбордов Grafana.

Какие источники данных поддерживаются

На момент выхода материала CaptainBridge имеет адаптеры для следующих систем:

  • Jira — популярный трекер задач и багов.

  • Redmine — альтернатива Jira с открытым исходным кодом.

  • GitLab — система контроля версий и управления проектами.

Еще я разрабатываю интеграции еще примерно с десятью источниками данных, включая:

  • GitHub — крупнейшую платформу для хостинга проектов и коллаборации разработчиков.

  • Bitbucket — облачный сервис для хранения и совместного редактирования кода.

  • Telegram, Slack, Discord — платформы корпоративного общения и обмена сообщениями.

Как устроен конструктор запросов

Это ключевой компонент CaptainBridge, потому что именно благодаря ему удобно обрабатывать и анализировать данные. Разберу устройство конструктора.

Что такое этап запроса

Запрос в CaptainBridge — последовательность этапов, а этап — операция над данными: фильтр, группировка, объединение и другие стандартные действия.

Структура этапов

Каждый этап запроса может выполнять следующие роли:

  1. Получение входных данных. Они поступают либо из внешней системы (например, из задач в Jira), либо из результата предыдущих этапов. Такая архитектура позволяет строить каскадные запросы, используя выход одного этапа как вход следующего.

  2. Обработка данных. Поддерживаются три способа ее описания:

    • Mongo Aggregation Pipeline — для сложных запросов с фильтрацией, группировкой и трансформированием данных.

    • Python — для сложной логики и нестандартных операций.

    • RPC (Remote Procedure Call) — вызывает внешние сервисы для дополнительной обработки данных.

    • Планируется поддержка SQL-запросов и no-code-интерфейсы.

  3. Предоставление промежуточных или итоговых результатов всего запроса. Особенность конструкции в том, что один из этапов помечается как терминальный, и его результат станет итоговым выводом всего запроса. Остальные этапы формируют промежуточные данные, необходимые для нужного результата.

Почему такой подход удобен

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

Проще разрабатывать сложные запросы, так как:

  • можно комбинировать данные из разных источников (например, задачи из Jira плюс сообщения из Slack), что открывает широкие перспективы для комплексного анализа;

  • доступны любые требуемые сценарии, будь то фильтрация данных, специфичные расчеты или получение информации из сторонних сервисов.

Ух, скорее всего, выглядит сложно. Но довольно просто, если разобраться. Чтобы стало понятнее, покажу, как эта схема работает на практике.

Практический пример: Developer Cycle Time

Реализуем популярную метрику — Developer Cycle Time, среднее время разработки одной фичи.

Допустим, наша команда использует Jira, где разработчики меняют статус задачи следующим образом:

  • когда начинается разработка, ставят In Progress;

  • по окончании разработки переводят в Closed.

Тогда наша цель — выяснить, как меняется средняя разница времени между In Progress и Closed у одной и то же задачи.

Сначала подключаем Jira к CaptainBridge в качестве источника данных, как описано в документации. И ждем, пока закончится первая индексация данных: после нее данные скопируются в локальную базу и к ним можно будет делать запросы.

Далее создаем непосредственно запрос к данным.

Разобьем запрос на два этапа для простоты.

Этап № 1: фильтрация закрытых задач за заданный период, назовем это Closed issues. Отфильтруем все задачи, закрытые в выбранный временной диапазон:

В качестве входящих данных укажем таблицу Jira issues из локального хранилища. Она содержит данные всех задач, скопированные из трекера Jira. Структура таблицы описана в документации.

Поле Action type выставляем в значение query, что дает нам написать запрос к таблице Jira issues, используя синтаксис Mongo Aggregation Pipeline.

Посмотрим внимательно на запрос, написанный для этой стадии:

[
  {
    "$match": {
      "project": "COSMOS",
      "changes": {
        "$elemMatch": {
          "created": {
            "$gt": "$$datetime_from",
            "$lte": "$$datetime_to"
          },
          "field": "status",
          "value_to_string": {
            "$in": [
              "Closed"
            ]
          }
        }
      }
    }
  }
]

В строке 3 мы используем оператор $match, оставляющий только те записи таблицы Jira issues, у которых:

  • поле project содержит значение COSMOS (так называется наш проект в Jira);

  • среди изменений задачи, которые хранятся в поле changes, есть на выбранном интервале дат хотя бы одно изменение поля status на значение Closed.

Обратите внимание на конструкции $$datetime_from и $$datetime_to в 8-й и 9-й строке. Это обращение к переменным запроса, среди которых всегда есть начало и конец временного интервала, на котором вычисляется запрос.

Кнопка Show result of this stage вычислит этот этап запроса и покажет его результат в виде таблицы. Это очень удобно для отладки запроса:

Этап №2: Cycle time, вычисление медианы длительностей между статусами.

Пройдемся в цикле по задачам, полученным на предыдущем этапе, и вычислим разницу между таймстемпами первого перехода в статус In Progress и последнего перехода в Closed для каждой из них. А после получим медиану этих значений.

Для этого поле Input type ставим равным stage, а в выпадающем списке поля Stage name выбираем имя предыдущего этапа, то есть Closed issues. Таким образом мы направляем результат работы предыдущей стадии в описываемую стадию. Для удобства обращения к данным мы назовем результат предыдущей стадии issues, это значение нужно указать в поле Alias.

В поле Action type выставляем значение python, что дает нам написать обработчик запроса на языке Python. На нем написать задуманный цикл с вычислением медианы проще простого.

Посмотрим внимательно на код обработчика:

import statistics

issues = INPUTS['issues']
print(f"issues are {issues}")
result = {}
for issue in issues:
    issue_type = issue['issue_type']['name']

    started_at = None
    finished_at = None
    for ch in issue['changes']:
        if started_at is None and ch['field'] == 'status' and ch['value_to_string'] == 'In progress':
            started_at = ch['created']

        if ch['field'] == 'status' and ch['value_to_string'] == 'Closed':
            finished_at = ch['created']

    if started_at and finished_at:
        result.setdefault(issue_type, []).append((finished_at - started_at).days)
 
return [
    {'label': issue_type, 'value': statistics.median(values)}
    for issue_type, values in result.items()
]
  • В строке 3 мы кладем в переменную issues результат работы предыдущей стадии, используя значение issues, указанное в поле Alias блока Inputs.

  • В строке 6 мы итерируем по всем задачам, отфильтрованным на предыдущем этапе.

  • В строке 11 мы смотрим на все изменения очередной задачи и запоминаем, когда статус стал In Progress первый раз (строка 13) и Closed последний раз (строка 16).

  • В строке 21 мы вычисляем нужную нам медиану и запаковываем результат в требуемый формат, чтобы полученный запрос можно было отобразить на диаграмме.

Обратите внимание, что в обработчике можно импортировать любые стандартные модули, а также функцией print выводить промежуточные результаты и отладочную информацию, как это сделано в строке 4.

Сохраняем запрос, не забыв указать в качестве терминальной стадии именно вторую, Cycle time.

Чтобы визуализировать полученный запрос, добавим его на дашборд либо дождемся, пока создатели CaptainBridge интегрируют «Графану».

При добавлении на дашборд получится вот такой симпатичный график, который показывает, как меняется длительность разработки во времени:

Что дальше

Чтобы начать работу с инструментом, посетите соответствующий репозиторий на GitHub, где подробная документация описывает, как подключать источники данных и писать запросы к ним. А еще там вы найдете контакты разработчиков — обращайтесь непременно! На данном этапе обратная связь, даже токсичная и негативная, максимально полезна.

И да пребудет с вами сила!

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


  1. VitaminND
    24.06.2025 08:05

    Умиляют попытки менеджеров оцифровать работу программиста. Вон каменщика или копальщика канав цифруйте.

    Вася сделал задачу1 за 5 часов (первоначальная оценка 3 часа), Петя сделал задачу2 за 8 часов (первоначальная оценка 14 часов).

    Что следует из этих цифр? Ничего.

    Была ли полная постановка задачи, чтобы утверждать, что оценка правильная? Не встретились ли засады при работе с инструментом от вендора? ... да еще много бывает засад...

    Shit in - shit out.

    На выходе из инструмента - фейк, так как на входе - фейк.


    1. sunnybear
      24.06.2025 08:05

      По-хорошему, достаточно двух метрик: доволен ли бизнес работой команды (бинарно или от 1 до 10) и довольна ли команда своей работой (бинарно или от 1 до 10). Если нет или не 10, то берётся обратная связь, и по ней действуется. Остальное - от лукавого


    1. DartPanda Автор
      24.06.2025 08:05

      Сам программист и понимаю о чём вы говорите. Но попробуйте поставить себя на место работодателя. Он платит детерминированную зарплату и имеет право ожидать предсказуемого результата. Если результат непредсказуем, то невозможно строить бизнес. Основная задача менеджера, коим я тоже являюсь, состоит в том, чтобы добиваться предсказуемого и стабильного результата. Отсюда менеджер должен строить и организовывать работу команд так, чтобы они выдавали предсказуемый и измеримый результат.


      1. kompilainenn2
        24.06.2025 08:05

        Ну и как? Получается строить команды? А результата (законченный проект имеется ввиду) с такими построенными командами вы достигаете?