Создание пользовательских элементов рабочего процесса

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

  • доступные действия на каждом шаге
  • пользователи / группы, которые могут выполнять переход рабочего процесса
  • функции, выполняемые по завершении перехода рабочего процесса

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

Обзор

Чтобы создать пользовательский элемент рабочего процесса (например, условие, пост-функция), доступный в JIRA, вам необходимо создать надстройку с модулем рабочего процесса. Надстройка будет состоять из следующих компонентов (все они содержатся в одном JAR):

  • Дескриптор надстройки для включения модуля рабочего процесса в JIRA
  • Классы Java, которые инкапсулируют логику элемента рабочего процесса
  • Шаблоны ресурсов для отображения элемента рабочего процесса

Они описаны более подробно ниже.Условия рабочего процессаУсловие ограничивает выполнение перехода рабочего процесса до тех пор, пока не будут выполнены определенные критерии. Если условие не не выполнится, ссылка перехода не будет доступна на странице «Просмотр задачи».

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

Системные условия

JIRA предоставляет ряд системных условий, доступных при настройке - DisallowIfInStepCondition, AllowOnlyAssignee, IssueAssignedCondition и т. д., каждое из которых позволяет пользователю определять, когда становится доступным переход рабочего процесса.

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

Например, ссылка перехода рабочего процесса «Закрыть задачу» для задачи может быть доступна только в том случае, если все связанные подзадачи связаны с статусом «Закрыто». По сути, эта  ссылка на переход недоступна для родительской задачи, пока все подзадачи не будут закрыты.

Пользовательские условия рабочего процесса

Для разработчиков, разрабатывающих пользовательское условие рабочего процесса, мы рекомендуем, чтобы класс пользовательских условий расширил класс AbstractJiraCondition. Чтобы избежать множественных вызовов базы данных для извлечения исходного объекта задачи для проверки состояния, для конструктора условий доступны две возможности. Во-первых, пользовательский класс условий может перезаписать следующий метод:


Issue getIssue(Map transientVars)

Логика в этом методе должна запрашивать исходный объект задачи по мере необходимости.

В качестве альтернативы, если метод getIssue не перезаписан, можно передать исходный объект задачи  карте transientVars, например:


GenericValue origianlIssueGV = ComponentManager.getInstance().getIssueManager().getIssue(issue.getId());
fields.put(AbstractJiraCondition.ORIGNAL_ISSUE_KEY, IssueImpl.getIssueObject(origianlIssueGV));

В этом случае объект fields будет передан методу getIssue в качестве карты transientVars.

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

Пример - условие блокировки родительских задач

Этот пример обеспечивает обратное условие функции SubTaskBlockingFunction, поскольку он определяет, доступен ли переход для подзадачи на основе статуса связанной с ней родительской задачи.

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

Условие применяется к переходу «Переоткрыть» в копии рабочего процесса JIRA по умолчанию, связанного с типом задачи «Подзадача».

РИСУНОК

Фактически это условие предотвратит переход для любой подзадачи из статуса «Закрыто» в «Повторно открытое», если родительская задача не связана с неразрешенным статусом.

Логика условия

Логика условий содержится в классе ParentIssueBlockingCondition, который реализует интерфейс Condition.

Единственным методом, требующим реализации, является метод passesCondition(...). В этом примере этот метод извлекает родительскую задачу, а затем определяет, содержится ли связанный с ней статус в указанном пользователем списке статусов. Условие переходит, если указанный список статусов содержит статус, связанный с родительской задачей.

Список статусов указан при добавлении условия рабочего процесса к переходу.

Также включен класс WorkflowParentIssueBlockingConditionFactoryImpl - этот класс управляет передачей необходимых параметров шаблонам ресурсов.

Ресурсы условий

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

В этом примере для каждого экрана предоставляется шаблон velocity:

  • templates/issueblockingcondition/issueblockingcondition-input-params.vm
  • templates/issueblockingcondition/issueblockingcondition-edit-params.vm
  • templates/issueblockingcondition/issueblockingcondition-view.vm

позволяя пользователю изначально указывать статусы, которые приведут к «проходу», для редактирования этих статусов, а также экран, отображающий выбранные статусы.

Дескриптор надстройки

Как и во всех плагинах, условие рабочего процесса должно быть определено в файле с именем atlassian-plugin.xml и находиться в корневом каталоге JAR-файла.

Определение условия ParentIssueBlockingCondition выглядит следующим образом:


<atlassian-plugin key="com.atlassian.jira.plugin.workflow.example" name="Workflow Examples Plugin">
<plugin-info>
<description>Example JIRA Workflow Elements</description>
<version>1.0</version>
<application-version min="3.0" max="3.0"/>
<vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com"/>
</plugin-info>

<workflow-condition key="issueblocking-condition" name="Parent Issue Blocking Condition"
class="com.atlassian.jira.plugin.workflow.example.WorkflowParentIssueBlockingConditionFactoryImpl">
<description>Condition to block sub-task issue transition depending on
parent issue status.</description>

<condition-class>
com.atlassian.jira.plugin.workflow.example.condition.ParentIssueBlockingCondition</condition-class>

<resource type="velocity" name="view"
location="templates/issueblockingcondition/issueblocking-condition-view.vm"/>
<resource type="velocity" name="input-parameters"
location="templates/issueblockingcondition/issueblocking-condition-input-params.vm"/>
<resource type="velocity" name="edit-parameters"
location="templates/issueblockingcondition/issueblocking-condition-edit-params.vm"/>
</workflow-condition>
</atlassian-plugin>

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

Класс, содержащий логику условий, ParentIssueBlockingCondition, указан ниже в теге <condition-class>.

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

Условие блокировки родительских задач в действии

После того, как JAR-файл примера рабочего процесса был помещен в каталог JIRA lib, условие блокировки родительских задач будет доступно как условие в редакторе рабочего процесса.

Пост-функции  рабочего процесса

Функция post выполняет указанные действия сразу после выполнения перехода (следовательно, функция post name). Примеры возможных действий включают в себя обновление полей задач, создание истории изменений, добавление комментария, создание события, которое сигнализирует о том, что задача прошла через рабочий процесс и т. д.

В этом разделе учебника основное внимание уделяется элементу * post function * и предоставляется пример пост функции, которая может быть подключена к JIRA.

Функции post системы JIRA

JIRA предоставляет ряд функций post, доступных при настройке - UpdateIssueStatusFunction, CreateCommentFunction и т. д. - каждая из которых позволяет пользователю указать, что определенные действия должны выполняться после определенного перехода рабочего процесса.

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

Пример. Закрытие функции post родительской задачи.

Эта пример функции post  закроет родительскую задачу после закрытия заключительной подзадачи (все остальные связанные подзадачи уже закрыты).

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

Функция post может быть применена к переходу «Close Issue»(Закрыть задачу) в копии рабочего процесса JIRA по умолчанию, связанного с типом задачи «Подзадача».

РИСУНОК

Логика функции Post

Логика пост-функции содержится в классе класса CloseParentIssueFunction, который реализует интерфейс FunctionProvider.

Метод execute извлекает подзадачу из параметров. Исходя из этого определяется родительская задача и выполняется проверка того, закрыта ли родительская задача или нет.

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

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

Единственный пользовательский ввод - связать функцию post с конкретным переходом в рамках рабочего процесса.

Ресурсы пост-функции

Эта пост-функция требует только шаблона представления, так как нет необходимости в настройке или редактировании.

Для экрана просмотра предусмотрен шаблон velocity:

  • templates/closeparentfunction/closeparentissue-function-view.vm

Дескриптор надстройки

Как и во всех надстройках, условие функции post должно быть определено в файле с именем atlassian-plugin.xml и находиться в корневом каталоге JAR-файла.

Определение условия CloseParentIssueFunction следующее:


...
<workflow-function key="closeparentissue-function" name="Close Parent Issue Function"
class="com.atlassian.jira.plugin.workflow.WorkflowNoInputPluginFactory">
<description>Closes the parent issue on closing final associated
sub-task (all other sub-tasks are closed).</description>

<function-class>
com.atlassian.jira.plugin.workflow.example.function.CloseParentIssueFunction</function-class>

<orderable>true</orderable>
<unique>true</unique>
<deletable>true</deletable>
<default>true</default>

<resource type="velocity" name="view"
location="templates/closeparentfunction/closeparentissue-function-view.vm"/>
</workflow-function>
...

Здесь класс WorkflowNoInputPluginFactory должен реализовывать WorkflowPluginFunctionFactory.

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

Класс, содержащий логику постфункции, CloseParentIssueFunction, указан ниже в теге <function-class>.

Также можно настроить функцию post, как она появляется в редакторе рабочего процесса. Можно указать следующие опции:

  • orderable - указывает, может ли эта функция post быть переупорядочена в списке post функций, связанных с переходом. Помещение в списке определяет, когда функция post фактически выполняется.
  • unique - указывает, является ли эта функция сообщения уникальной или нет - т. е. если можно добавить несколько экземпляров этой пост-функции на один переход.
  • deleteable - указывает, может ли функция post удалина из перехода.
  • default - указывает, будет ли эта функция post автоматически ассоциироваться с любыми новыми созданными переходами.

Также можно указать параметр конфигурации веса weight - однако это в основном предназначено для элементов пост функции системы JIRA. Этот параметр используется в сочетании с параметром по умолчанию default - если функция post должна быть добавлена ко всем новым переходам, параметр веса  weight используется для определения позиции постфункции в списке функций post.

Наконец, указывается расположение ресурса шаблона представления .

Закрыть пост функцию родительской задачи  в действии

После того, как файл JAR примера рабочего процесса был помещен в каталог JIRA-lib, функция «Закрыть родительскую задачу» будет доступна в качестве функции post в редакторе рабочего процесса.

Держите задачу открытой, пока все что ее блокирует не будет закрыто

Эта пользовательская подпрограмма может использоваться, чтобы не позволять пользователям переходить к задаче, которая связана посредством linktype «Блокировка» с задачами, не указанными в указанном пользователем состоянии. (вы можете легко расширить эту подпрограмму, чтобы также указать тип ссылки)

При использовании условия в рабочем процессе JIRA пользователь должен предоставить единственный параметр closedstate, который содержит значение первичного ключа «id» (1 или более разделенных запятыми) состояния (ий) задач,  связанные с ними задачи должны быть включены, чтобы разрешить  переход рабочего процесса.

Существует один недостаток этой простой задачи: невозможно переопределить условие. Само по себе это нормально, так как вы можете просто использовать его в предложении «или» с дополнительной проверкой для групп, членом которых является пользователь. Если, однако, вы хотите иметь более сложную логику - т. е. .:

если (пользователь является членом группы админ- проекта и все подзадачи закрыты) ИЛИ (пользователь является jira-администратором)

if (the user is a member of project-admin group AND all subtasks are closed) OR (user is a jira-administrator)

вам нужно создать класс «мета-условие», который содержит логику для условий «и». Предполагается, что поддерживается  синтаксис с помощью библиотеки Javascript, но я еще не определил, что это такое.

Как использовать

Каждый переход, который использовал это условие, требовал сложной логики if / else, поэтому мне пришлось создать «мета-условие», содержащее вызовы умножащее условности. Ваша окружающая среда может отличаться. Независимо от того, чтобы использовать это условие, вызовите его из класса java, как показано в скелете на связанной странице, показанной ниже.

В разделе «Скелет условия пользовательского рабочего процесса» ниже на этой странице есть фрейм, необходимый для использования следующей подпрограммы:

blockingLinksClosed подпрограмма


    public boolean blockingLinksClosed( GenericValue issue
                                       , String linkValidStates )
    {
        try
        {
            // Inward links are links made from other issues to the passed 'issue'
            List inwardLinks = ComponentManager.getInstance().getIssueLinkManager().getInwardLinks(issue.getLong("id"));

            // Make sure all linked issues of link type equal to passed link name
            for (int i = 0; i < inwardLinks.size(); i++)
            {
                IssueLink link = (IssueLink) inwardLinks.get(i);
                //log.error("issueLinkName: " + link.getIssueLinkType().getName());
                if("Blocking".equals(link.getIssueLinkType().getName()))
                {
                    String issueStatus = ((GenericValue ) link.getSource()).getString("status");
                    //log.error("issueStatus: " + issueStatus);

                    boolean isClosed = false;
                    String[] validStates = linkValidStates.split(",");
                    for (int j = 0; j < validStates.length; j++)
                    {
                        String validState = validStates[j];
                        if(issueStatus.equals(validState))
                        {
                            //log.error("  validState: " + validState);
                            isClosed = true;
                            break;
                        }
                    }
                    //log.error("  returning: " + isClosed);
                    return isClosed;
                }
            }
            return true;
        }
        catch(Exception e)
        {
            log.error("Exception verifying all blockingLinks are closed: " + e, e);
            return false;
        }
    }

Инструкции по установке

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

Пользовательский скелет условий рабочего процесса

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

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

blockingLinksClosed подпрограмма


package com.newisys.jira.workflow.condition;

import org.apache.log4j.Category;
import org.ofbiz.core.entity.GenericValue;
import org.ofbiz.core.entity.GenericEntityException;

import com.opensymphony.workflow.Condition;
import com.opensymphony.workflow.spi.WorkflowEntry;
import com.opensymphony.module.propertyset.PropertySet;

import com.atlassian.jira.ManagerFactory;
import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.issue.link.IssueLink;
import com.atlassian.jira.issue.link.IssueLinkType;

import java.util.Map;
import java.util.List;

import java.util.Iterator;
import java.util.Collection;

/**
 * Passes if all subtask issues have current status equal to passed status id
 *
 *  required argument 'closedstate'
 */
public class BlockingLinksClosedCondition implements Condition
{
    private static final Category log = Category.getInstance(BlockingLinksClosedCondition.class);

    public boolean passesCondition(Map transientVars, Map args, PropertySet ps)
    {
        try
        {
            WorkflowEntry entry = (WorkflowEntry) transientVars.get("entry");
            GenericValue issue = null;
            try
            {
                issue = ManagerFactory.getIssueManager().getIssueByWorkflow(new Long(entry.getId()));
            }
            catch (GenericEntityException e)
            {
                log.error("Exception: " + e, e);
                return false;
            }

            // Get the id of the workflow
            String closedState = (String) args.get("closedstate");
            if (closedState == null) throw new IllegalArgumentException("Must specify a 'closedstate' arg specifying ids of valid states");

RETURN RESULT OF CALL TO WORKFLOW CONDITION SUBROUTINE 

        }
        catch(Exception e)
        {
            log.error("Exception: " + e, e);
            return false;
        }

        return true;
    }
}

Похожие темы

Вы также можете прочитать учебное пособие, которое расширяется при работе с надстройкой JIRA:

  • Создание пользовательского типа поля

По материалам Atlassian JIRA  Server Developer Creating custom workflow elements