Введение

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

Если вы не читали статью про базовую автоматизацию, то советую сначала прочитать ее.

Анализ результатов работы с базовой автоматизацией

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

Что я для себя выделил:

  1. Когда я добавляю метку ревью на ТЗ — оно сразу становится «В работе».

    1. Но я ведь могу не сразу начать работать с ТЗ, если я создал структуру страниц заранее. И тогда в общем макросе страницы, которые в ожидании, начинают смешиваться со страницами, которые действительно в работе. Значит, мне нужен новый статус для формирования беклога ТЗ.

  2. Если участник команды прочитал ТЗ и оставил там комментарии, то эта информация нигде не отображается. Нет отслеживания обработанных страниц.

  3. Так как процесс ревью был внедрен не с самого начала написания документации, то и на ревью сразу упало много страниц, а большой объем работы, как мы знаем, отталкивает — нужно было как-то приоритизировать, что читать в первую очередь, и ограничить пачку ТЗ на ревью.

  4. И главной занозой оставалась ситуация, когда мне приходилось собирать информацию о прочтении всего ТЗ командой — я хотел информирование о том, что ТЗ прочли все, и я могу уже разбирать замечания.

Результат решения поставленных задач

Схема процесса

Код этого процесса

{workflow:name=Template for Habr|key=spaceworkflow--1938429722|label=habr_temp|adminusers=IKhakharev|stickylabels=habr_temp|content=pages}
    {description}
        Workflow: Wait_for_work, In Work, Review, Approved
    {description}
    {pagefooter:visibility=all}
        ||Document status|{pagestatus}|
        ||Author|@author@|
        ||Approve Counter|@Approve Counter@|
        ||Counter|@Counter@|
        ||Parameter value|@Control@|
        ||Parameter value 2|@Review_read@|
        ||Approved by|@Review>approvers@|
        ||Reject by|@Review>rejectors@|
        ||Assine users|@Review > approvalassignees@|
        ||Assine users QTY-1|@Assine users QTY-1@|
    {pagefooter}
    {workflowparameter:Control|type=list|options=Wait_mode,Manual_approve,Notify_read_again,In_work,Initialization,Back_to_backlog|edit=true}
        Wait_mode
    {workflowparameter}
    {workflowparameter:Review_read|type=list|options=not_in_review,on_read,all_read}
        
    {workflowparameter}
    {state:Wait_for_work|taskable=true|colour=#4A6785}
        {state-selection:states=In Work}
    {state}
    {state:In Work|taskable=true}
        {state-selection:states=Review}
    {state}
    {state:Review|approved=Approved|taskable=true|colour=#0052CC}
        {state-selection:states=In Work|user=ikhakharev}
        {approval:Review|user=&IKhakharev|approvelabel=Ставлю Approve|rejectlabel=Оставлены комменты}
    {state}
    {state:Approved|final=true|hideselection=true}
        {state-selection:states=In Work|user=ikhakharev}
    {state}
    {trigger:labeladded|label=review}
        {set-metadata:Control}Wait_mode{set-metadata}
        {set-metadata:Review_read}not_in_review{set-metadata}
        {set-metadata:Counter}0{set-metadata}
        {set-metadata:Approve Counter}0{set-metadata}
        {set-metadata:Assine users QTY-1}7{set-metadata}
    {trigger}
    {trigger:statechanged|state=In Work}
        {set-metadata:Counter}0{set-metadata}
        {set-metadata:Approve Counter}0{set-metadata}
        {set-metadata:Control}Wait_mode{set-metadata}
    {trigger}
    {trigger:statechanged|state=Review}
        {set-metadata:Review_read}on_read{set-metadata}
        {send-email:user=@Review > approvalassignees@|subject=HABR test}
        Назначена на ревью: @page@
        {send-email}
    {trigger}
    {trigger:statechanged|state=Approved}
        {set-metadata:Control}Wait_mode{set-metadata}
        {set-metadata:Counter}0{set-metadata}
        {set-metadata:Approve Counter}0{set-metadata}
        {remove-label:all_read}
        {remove-label:pri_1}
        {remove-label:@user@}
        {set-metadata:Review_read}not_in_review{set-metadata}
        
        {send-email:user=@Review > approvalassignees@|subject=HABR test}
        Ревью завершено: @page@
        {send-email}
    {trigger}
    {trigger:pageparameterupdate|parameter=Control|@Control@=Manual_approve}
        {set-state:Approved}
    {trigger}
    {trigger:pageparameterupdate|parameter=Control|@Control@=In_work}
        {set-state:In Work}
    {trigger}
    {trigger:pageparameterupdate|parameter=Control|@Control@=Back_to_backlog}
        {set-state:Wait_for_work}
        {set-metadata:Control}Wait_mode{set-metadata}
        {set-metadata:Counter}0{set-metadata}
        {set-metadata:Approve Counter}0{set-metadata}
        {remove-label:all_read}
        {remove-label:pri_1}
        {remove-label:@user@}
        {set-metadata:Review_read}not_in_review{set-metadata}
    {trigger}
    {trigger:pageparameterupdate|parameter=Control|@Control@=Initialization}
        {set-metadata:Control}Wait_mode{set-metadata}
        {set-metadata:Review_read}on_read{set-metadata}
        {set-metadata:Counter}0{set-metadata}
        {set-metadata:Approve Counter}0{set-metadata}
        {set-metadata:Assine users QTY-1}7{set-metadata}
    {trigger}
    {trigger:pageparameterupdate|parameter=Control|@Control@=Notify_read_again}
        {set-metadata:Control}Wait_mode{set-metadata}
        {remove-label:all_read}
        {set-metadata:Review_read}on_read{set-metadata}
        {set-metadata:Counter}@Approve Counter@{set-metadata}
        
        {send-email:user=@Review_WCM>rejectors@|subject=Web Campaign ревью ТЗ}
        На все вопросы ответил, можно перечитывать и закрывать комменты
        
        Страница: @page@
        {send-email}
    {trigger}
    {trigger:pageapproved|approval=Review_WCM|partial=true|@Counter@=@Assine users QTY-1@}
        {set-label:all_read}
        {set-metadata:Review_read}all_read{set-metadata}
        {set-metadata:Counter}@Approve Counter@{set-metadata}
    {trigger}
    {trigger:pageapproved|approval=Review_WCM|partial=true|@Review_read@=on_read}
        {increment-metadata:Counter|increment=1}
        {increment-metadata:Approve Counter|increment=1}
        {remove-label:@user@}
    {trigger}
    {trigger:pagerejected|approval=Review_WCM|partial=true|@Counter@=@Assine users QTY-1@}
        {set-label:all_read}
        {set-label:@user@}
        {set-metadata:Review_read}all_read{set-metadata}
        {set-metadata:Counter}@Approve Counter@{set-metadata}
    {trigger}
    {trigger:pagerejected|approval=Review_WCM|partial=true|@Review_read@=on_read}
        {increment-metadata:Counter|increment=1}
        {set-label:@user@}
    {trigger}
    {trigger:labeladded|label=all_read|state=Review_WCM}
        {send-email:user=Ikhakharev|subject=Web Campaign ревью ТЗ}
        Все пользователи оставили Approve или Reject(+)
        
        Страница: @page@
        {send-email}
    {trigger}
{workflow}

Разбор получившегося БП и кода

В самом начале видим (строки 2-4), что набор статусов в WorkFlow увеличился. Там появился новый статус Wait_for_work. Теперь при добавлении метки ревью на документ он автоматически находится в статусе ожидания взятия в работу. Таким образом я организовал возможность складывать заранее созданные страницы в собственный беклог, и они не мешаются мне в макросе всех документов в процессе ревью.

Далее у нас появился раздел Pagefooter.

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

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

Что же я добавил в Pagefooter (строки 5-16).

  • Document status|{pagestatus} — динамически вытаскивается текущий статус процесса ревью документа.

  • Author|@author@ — указывается автор страницы (создатель).

  • Approve Counter|@Approve Counter@ — эту переменную я использую как внутренний счетчик кликов на кнопку «Ставлю Approve» на этапе ревью ТЗ.

  • Counter|@Counter@ — эту переменную я использую как общий счетчик кликов, не важно, участник команды нажал на «Ставлю Approve» или «Оставлены комменты (Reject)».

  • Parameter value|@Control@ — это параметр, доступный для управления извне. Он помогает управлять процессом из самого документа (подробнее разберем чуть ниже).

  • Parameter value 2|@Review_read@ — этот параметр является внутренним и нужен для условий сравнения в триггерных событиях.

  • Approved by|@Review>approvers@ — раздел, в который я списком вывожу логины тех участников, которые нажали на «Ставлю Approve». Строение условия формируем так — из статуса Review укажи всех, кто нажал на «Ставлю Approve».

  • Reject by|@Review>rejectors@ — раздел, в который я списком вывожу логины тех участников, которые нажали на «Оставлены комменты (Reject)». Строение условия формируем так — из статуса Review укажи всех, кто нажал на «Оставлены комменты (Reject)».

  • Assine users|@Review > approvalassignees@ — раздел, в который вывожу списком всех участников команды, которые были назначены на ревью данного документа. Назначение происходит в момент перевода документа в статус Review и сохраняется до нового цикла, то есть будет сохранено в статусе Approve и сбросится при возврате в стату «В работе».

  • Assine users QTY-1|@Assinee users QTY-1@ — переменная, в которую я числом сохраняю количество участников ревью -1. Так как в процессе счет идет с 0.
    P.S. В моем случае я могу так сделать, так как у меня всегда все участники команды проводят ревью (они назначаются автоматически) и значение этой переменной фиксировано.

Далее мы проявляется параметры (workflowparameter) (строки 17-22).

  • Первый параметр Control — доступен для редактирования снаружи, то есть я могу им управлять из окна работы с процессом документа в самом документе.

    • Значение заданы списком:

      • Wait_mode — дефолтное значение параметра, в которое он будет всегда возвращаться после любого совершенного события.

      • Manual_approve — этот параметр позволяет вручную перевести статус документа в Approve (потребность появилась, когда разработчик был в долгом отпуске, а ТЗ уже было готово к разработке, но без Апрува мы его в работу не берем).

      • Notify_read_again — данное значение отправляет рассылку участникам команды о том, что я ответил на все комментарии по ревью. При этом отправка будет только тем, кто нажал на кнопку «Оставил комментарии (Reject)». Те, кто оставил Approve, будут исключены из рассылки.

      • In_work — данный параметр принудительно возвращает документ «В работу».

      • Initialization — данное значение инициализирует состояние всех параметров, счетчиков и необходимых показателей в дефолтное значение. Очень удобно, когда процесс дорабатывался после перехода документа в статус Review.

      • Back_to_backlog — данное значение возвращает документ в беклог, если мы решили, что сейчас эта задача в менее высоком приоритете и документ только отвлекает команду.

  • Второй параметр Review_read — сделан как внутренний, то есть не может быть отредактирован извне. Изменение значений настроено внутри процесса, так как он используется для условий сравнения в триггерах.

    • Значения заданы списком:

      • not_in_review — это значение установлено, когда документ не находится на этапе Review.

      • on_read — установлено, когда документ находится на этапе ревью, но еще не все участники команды его прочитали.

      • all_read — устанавливается, когда все участники команды прочитали документы и указали какое-либо значение по ревью — «Ставлю Approve» или «Оставлены комменты (Reject)».

Далее в строчках 23-35 идет описание переходов между статусами, которое мы настраивали через визуальный конструктор. Их настройка описана в статье по базовой настройке процесса, по этому тут мы останавливаться не будем.

Инициализация данных при назначении документу процесса ревью (строки 36-42)

Так как в нашем процессе появилось уже достаточно переменных, то хорошо бы их инициализировать в момент присвоения процесса ревью на документ. Для этого используется триггер по событию присвоения метки review {trigger:labeladded|label=review}. В рамках данного триггера мы просто инициализируем все необходимые нам параметры в нужное состояние, чтобы далее процесс работал корректно. Инициализация значения в коде происходит при помощи макроса set-metadata , который может установить значение в переменную. (значение всегда устанавливается как текстовое, так что работать с числами проблематично).

Управление данными и значениями в параметрах при переходах статусов (строки 43-66)

Здесь у нас сформирована группа однотипных триггеров, которые отлавливают событие смены статуса trigger:statechanged, причем не важно, чем вызвано это переключение —прямым процессом или внешним параметром. В рамках перехода статуса мы определяем, что нам нужно сделать с документом и какие параметры нужно перевести в какие значения.

Для работы со значениями мы используем такие макросы как:

  • set-metadata — установка значения для параметра.

  • remove-label — удаление метки с документа. На метках построен процесс маркировки документа, и когда он переходит в состояние согласования, все метки необходимо удалить.

  • send-email —отправка сообщения.

    • Обратите внимание, что для пользователя, которому должна быть совершена отправка сообщения, указана динамически отбираемая группа пользователей, которая назначена на ревью @Review > approvalassignees@.

Зачем я использую макрос удаления метки и какие изменения я сделал в макросе отправки сообщений, мы рассмотрим чуть дальше

Работа с внешним параметром (строки 67-101)

Формируем группу триггеров, которые обрабатывают внешние изменения параметра {trigger:pageparameterupdate|parameter=@Control@=Manual_approve}. Тут указано событие для триггера, название параметра и в какое значение он был установлен.

В рамках этих триггеров может быть:

  • Либо просто смена статуса, а последующий триггер смены статуса сам уже все настроит.

  • Либо смена статуса и дополнительная инициализация значений.

  • Либо рассылка.

  • Либо просто инициализация параметров.

  • Но при этом каждый процесс, вызванный изменением параметра, будет возвращать сам параметр в значение Wait_mode.

Работа с непосредственным согласованием или отклонением документа (строки 102-121)

Этот раздел отвечает за решение таких задач, как маркировка документа, который ты прочитал, но не согласовал, и информирование аналитика о прочтении документа всей командой.

Здесь сформированы группы триггеров, которые отлавливают клик пользователя на кнопки «Ставлю Approve» или «Оставлены комменты (Reject)». Оба триггера настроены на частичное отлавливаение partial=true, то есть на каждый клик каждого участника команды. Если выставить значение false — он сработает, только если все отклонили или все согласовали ТЗ.

Начнем со второй пары триггеров — триггеров Reject.

  • Второй по очереди триггер отклонения ловит все клики на «Оставлены комменты (Reject)», пока внутренний параметр Review_read равен значению on_read (условие сравнения триггера).

    • В рамках себя он:

      • Повышает общий счетчик кликов на кнопку решения по ревью, используя макрос {increment-metadata:Counter|increment=1}. Этот счетчик считает нам общее количество участников команды, которые приняли решение.

      • Через макрос {set-label: @User@} на страницу с ТЗ проставляется динамически сформированная метка, равная логину нашего пользователя в Confluence.

        • Наличие этой метки на странице нам позволит динамически настроить макрос списка документов, на которых участник команды оставил комментарии и нажал на «Оставлены комменты (Reject)».

        • Фильтр CQL для макроса со списком отклоненных документов выглядит так: approver = currentUser() and label = currentUser()

Первый по очереди триггер Reject отличается от второго тем, что у него условие сравнения проверяет, что общий счетчик кликов равен количеству всех участников команды @Counter = @Assine users QTY-1@. Это значит, что триггер выполнится только в тот момент, когда последний участник команды нажмет на свое решение по ревью документа.

Если мы попадаем в первый триггер отклонения, это означает, что вся команда прочитала ТЗ и последнее решение было «Оставлены комменты (Reject)». В рамках этого триггера:

  • Проставляется метка {set-label:all_read}. Эта метка:

    • Маркирует документ как прочтенный всей командой.

    • Эта маркировка проявляет документ как готовый к разбору замечаний аналитиком и отображается в отдельном макросе списка документом с CQL фильтром: label = all_read

  • Устанавливается метка пользователя, который отклонил документ.

  • Внутренний параметр Review_read устанавливается в значение all_read — и это нам гарантирует, что второй по очереди триггер на это же событие уже не выполнится, так как не пройдет проверку на состояние внутреннего параметра в своем условии.

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

Теперь рассмотрим первую пару триггеров — триггеры Approve

Принцип формирования первого и второго триггера аналогичен тому, как сформированы триггеры на отклонение.

  • Первый сработает только когда последний участник команды сделает свое решение.

  • Второй будет срабатывать во всех остальных случаях, если «Ставлю Approve» нажал не последний участник команды.

Что происходит внутри триггера на Approve:

  • Используя макрос increment, мы повышаем значение как общего счетчика принятых решений по ревью, так и повышаем значение счетчика кликов на «Ставлю Approve». Счетчик положительных кликов нам помогает понять, сколько участников согласны с ТЗ и более не участвуют в ревью.

  • Если это триггер на финальный клик — то общий счетчик будет приравнен к значению счетчика положительных кликов, чтобы на втором круге ревью считать уже только оставшихся участников, кто не согласовал ТЗ.

  • С помощью макроса удаления метки мы удаляем динамически сформированную метку текущего пользователя {remove-label: @User@}.

    • Если пользователь ранее нажимал на реджект и метка с его логином была проставлена на странице и в макросе списка было текущее ТЗ, то метка удалится и страница пропадет из макроса — как задача из TODO-листа.

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

Рассылка по факту прочтения всей командой (строки 122 - 128)

Последним блоком в процессе стоит триггер на проставление метки all_read. Эта метка выставляется в первый триггерах на согласование / отклонение ТЗ. Данный триггер просто отправляет рассылку аналитику, который ведет это ТЗ.

Возврат к параметрами и переходами статусов

Именно в данном месте хочется вернуться к параметрам, а именно к параметру Notify_read_again.

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

Назначение этого параметра — уведомить всех участников команды, которые нажали на «Оставлены комменты (Reject)» в документе о том, что аналитик разобрал все их вопросы и внес нужные правки, если требовалось. Такая необходимость возникла, чтобы команда не начинала отвечать на комментарии, пока аналитик идет по документу и все разбирает по очереди.

При активации этого параметра:

  • Удаляется метка all_read — тем самым документ теряет отметку, что документ все прочли. Документ пропадает из макроса со списком документов, готовых к разбору аналитиком.

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

  • Отправляется рассылка о том, что можно смотреть ответы и правки только тем пользователям, которые нажали на «Оставлены комменты (Reject)» (user=@Review>rejectors@).

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

Итого

За счет более глубокого погружения в функционал Comala удалось сделать процесс работы с ревью документации еще более удобным:

  1. Появилась возможность беклога документации за счет статусной модели.

  2. Появился список отревьюенных и отклоненных документов, который собирается динамически на основании персональных меток. Теперь всегда можно увидеть, что ты уже читал, а что будешь читать в первый раз.

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

  4. Сформирована автоматизированная рассылка по факту прочтения документа всей командой.

  5. Возможность гибко управлять процессом за счет внешнего параметра.

  6. Автоматизированная рассылка по обработанному документу только тем, кто отклонил в два клика (через внешний параметр).

  7. Работа с метками оказалась очень удобна в рамках CQL-фильтра, поэтому приоритизация ТЗ также была сделана через отдельную метку. Это помогло фильтровать документы на ревью и отображать их пачками для команды.

Полезные ссылки на документацию Comala

Проблемы, которые остались нерешенными на текущий момент

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

Самая главная для меня проблема — это невозможность работы с числами. Отсутствует такая функция как Count().

Почему она мне так нужна?

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

  • Все счетчики, которые я считаю вышел, при инициализации всегда задаются со значением 0.

  • Число общих участников, назначенных на ревью, задается вручную.

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

  • Count(Review>rejectors)

  • Count(Review > approvalassignees)

  • Count(Review>approvers)

то это позволило бы сделать

  • Инициализацию процесса полностью динамически сформированной. В любом момент процесса можно было бы ее провести и инициализировать счетчики.

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

В таком случае возможность работы с числами могла бы сделать процесс автоматизации ревью максимально автономным и с качественным формированием данных.

Если кто-то решал такую задачу — буду очень рад, если вы поделитесь своим решением!

Всем больше спасибо за внимание, надеюсь, что данная инструкция была вам полезна :-)

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